When bootstrap dropdown item is selected, button text disappears - html

I have a bootstrap dropdown menu. When I choose an item from the dropdown menu, the text of the button is changed to the text value of the choosen item (implemented by the help of this answer: https://stackoverflow.com/a/60029507/7061548).
I have to reload the window whenever an item from the dropdown menu has been chosen because I update the sessionstorage. This works fine after page reload. But I have a problem before reload. As soon as I click on an item from the dropdown menu, the menu button becomes fully green and the button text disappears just before the page is reloaded.
I found out that if I remove the class "btn-outline-success" from the button the problem seems to be gone. I have tried to apply css styles on various elements but didn't succeed in solving the problem.
Here is my code:
HTML:
<div class="m-dropdown">
<button type="button" class="btn btn-outline-succes m-btn dropdown-toggle" data-toggle="dropdown" aria-haspopup="true">
{{selectedColor}}
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" (click)="setColorGreen('Green')">Green</a>
<a class="dropdown-item" href="#" (click)="setColorBlue('Blue')">Blue</a>
</div>
</div>
TS:
...
selectedColor: string;
ngOnInit() {
this.selectedColor = "Green";
}
setColorGreen(color: string) {
this.selectedColor = "Green";
...
window.location.reload();
}
setColorBlue(color: string) {
this.selectedColor = "Blue";
...
window.location.reload();
}
...

As soon as I click on an item from the dropdown menu, the menu button becomes fully green and the button text disappears just before the page is reloaded.
When you click the button Bootstrap fires some JS to trigger the dropdown.
It's using the .dropdown-toggle class and the data-toggle="dropdown":
<button type="button" class="btn btn-outline-succes m-btn dropdown-toggle" data-toggle="dropdown" aria-haspopup="true">
I'm guessing that Bootstrap is marking the button as disabled so it can prevent double-clicks. And I'm guessing that the style change you're seeing is a result of some class changes that Bootstrap is making in JS as part of the button's click function.
If you have any defined CSS on the button, it could be conflicting with what Bootstrap is trying to do.
See the effect of clicking on a button on Bootstrap's docs page. Does your change look at all familiar?
You can further test this in Chrome DevTools by looking at what fires when you click the button. Then put a breakpoint there and click the button.
Or you can use DevTools to force element state on the button to see what happens in different states (:active, :hover, etc).

Related

Bootstrap dropdown height not updating with change in data - Angular

I'm sorry I'm reposting this but my previous question was marked as closed due to absence of minimal reproducible code. I have attached a stackblitz link with this one.
Original question - Bootstrap search-select dropdown but now when I try to search through the list and the dropdown opens on top, the list gets reduced and floats mid-air
Please don't close this one too. This is very important for my project and I have been stuck on this one for 16 hrs.
I have created a custom search select dropdown using the bootstrap dropdown in my Angular 8 project. The functionality works perfectly but the issue lies when the dropdown opens up on top of the element. Now when I search using the search box, the list gets updated accordingly and gets shortened but it keeps floating mid-air. You can see the issue in the GIF below.
Issue GIF - https://imgur.com/a/SeMVjns
As you can see in the first case when I search, the list gets shortened and it floats mid-air. What I want is when the list gets shortened, it still appear directly above the Expert dropdown button without any space in between.
Below is my HTML code:
<div class="dropdown h-100" [ngClass]="{'statusDropdownContainer': config.src != 'unavailability'}">
<a class="btn btn-white dropdown-toggle mb-2 statusFilterDropdown h-100 w-100 flex-middle" [ngClass]="{'btnDisable': disable, 'srcUnavailability': config.src == 'unavailability' }" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="text-truncate">{{config.src != 'unavailability' ? config.dropdownTitle + ':' : ''}} {{selectedValue ? selectedValue : 'All'}}</span>
</a>
<div class="dropdown-menu w-100 pt-0" aria-labelledby="dropdownMenuButton">
<input type="text" class="w-100 p-2 searchInput" [ngModel]="searchValue" (ngModelChange)="filterDropdown($event)" placeholder="{{config.placeholder}}">
<a *ngFor="let option of filteredList; let i=index" class="dropdown-item pointer text-contain" [ngClass]="{'alternateBackground': i%2 == 0 }" (click)="selectValue(option.name, option.unique_code)">
{{option.name}}
</a>
<div *ngIf="filteredList.length <=0" class="text-center text-muted mt-1">No {{config.user}} found</div>
</div>
</div>
What CSS changes do I need to make?
Stackblitz - https://stackblitz.com/edit/angular-ivy-nghkhp?file=src/app/app.component.html
Edit: I found the exact issue that's causing the problem. Bootstrap's dropdown uses popperJS to position the dropdown. PopperJS calculates the dropdown's position on the scroll event, so whenever the window scrolls, the dropdown's dimensions are recalculated (uses transform CSS). But in my case, since there is no scroll, the dimensions are not calculated and the dropdown has the same height as before the user searches something in the search box. But what I want is for the dimensions to be recalculated every time there is a change in the list's data. Any suggestions would be helpful. Thanks.
Bootstrap's dropdown uses popperJS to position the dropdown. PopperJS calculates the dropdown's position on the scroll event, so whenever the window scrolls, the dropdown's dimensions are recalculated (uses transform CSS).
One approach could be to give the height of the body slightly more than the viewport height and then programmatically trigger the scroll on ngModelChange
STACKBLITZ SOLUTION
You may add to CSS (You may also hide the vertical scroll-bar if not required with overflow-y: hidden;)
body {
min-height: 101vh;
}
And you might add scroll down 1px and scroll up 1px in your filterDropdown(e) method on ngModelChange as below to achieve the desired effect.
filterDropdown(e) {
console.log("e in filterDropdown -------> ", e);
window.scrollTo(window.scrollX, window.scrollY + 1);
let searchString = e.toLowerCase();
if (!searchString) {
this.filteredList = this.data.slice();
return;
} else {
this.filteredList = this.data.filter(
user => user.name.toLowerCase().indexOf(searchString) > -1
);
}
window.scrollTo(window.scrollX, window.scrollY - 1);
console.log("this.filteredList indropdown -------> ", this.filteredList);
}

How to fix the focus being reset to the top the page when the currently focused element is removed using ngIf in Angular?

I am working on improving the accessibility of an Angular Application. There is a cancel button, if pressed will hide the button and display div with a warning and two buttons, yes and no. If pressed no, the warning disappears and the cancel button re-appears.
I will add a small sample code snippet below. Actual code is a little complex.
<button *ngIf="!hideCancel" (click)="toggleWarning(true)" </button>
<div *ngIf="hideCancel">
<p>Are you sure you want to cancel?</p>
<button> Yes </button>
<button (click)="toggleWarning(false)"> No </button>
</div>
toggleWarning(flag: boolean) {
if (flag) {
document.getElementById('someWarning').innerHTML = 'Are you sure you want to cancel?';
} else {
document.getElementById('someWarning').innerHTML = '';
}
this.hideCancel= !this.hideCancel;
}
When using tab key and when focused on cancel button, when user presses enter key, the cancel button disappears and the below Warning appears. Then Chrome/Firefox, when I press the tab key, focus goes to the "No" button below the Warning as expected. But in IE, focus goes to the top of the page.
I am not getting why it is happening. Is it because the currently focused element(cancel button) is removed from the DOM hence the focus is reset to the top on tabbing?
Please help on how to prevent this in Internet Explorer.
There is a workaround to this by retaining the tab focus on the next element.
<button *ngIf="!hideCancel" (click)="toggleWarning(true);yesBtn.focus()">Cancel </button>
<!-- see how yesBtn reference is used to set the focus-->
<div *ngIf="hideCancel">
<p>Are you sure you want to cancel?</p>
<button #yesBtn> Yes </button>
<button (click)="toggleWarning(false)"> No </button>
</div>
Checkout the working example.

How to provide a clickable link (to some other page) on a dropdown-data-toggle?

I am trying to develop a dropdown data toggle, that can be used to open a dropdown-menu but simultaneously allows the user to click a link.
Currently, the link can only be opened via right-click "open in new tab". However, a left-click on the link always opens the dropdown, even though the browser recognizes the target of my link properly (I can see the target url in the bottom left of my browser).
I Already tried split-buttons, but their layout is not flexible enough.
Basically, I need one button (dropdown-toggle) with two lines.
The first line is an anchor with a link name and an url.
The second line is the actual label of the button.
(Background: When the user selects a new item from the dropdown, the displayed link and button label are updated by angular.)
<div class="btn dropdown-toggle" data-toggle="dropdown">
<a href="{{someUrl}}" class="button-link">
{{link name}}
</a>
<div class="button-label">
{{label}}
</div>
</div>
<ul class="dropdown-menu">
<li ng-repeat="item in items"> item </li>
</ul>
Expected result:
* If the user clicks on the link, I would like to load the linked page.
* If the user clicks somewhere else on the button (no matter whether its first or second line), I would like to open the dropdown menu.
However, a left-click on the link always opens the dropdown,
The <a> tag needs a click handler that stops propagation of the click:
<div class="btn dropdown-toggle" data-toggle="dropdown">
<a href="{{someUrl}}" class="button-link" ng-click="$event.stopPropagation()">
{{linkName}}
</a>
<div class="button-label">
{{label}}
</div>
</div>
This will prevent the click from bubbling to the <div> element that opens the dropdown.
For more information, see
MDN Web API Reference - Event.stopPropagation()

How to properly account for WCAG name, state, value, and role when using an icon as a toggle button

Frequently I'm faced with the task of using material icons and turning them into interactive buttons. For instance, there might be an icon that when clicked reveals text and rotates. When it's clicked again it rotates back to the original position and the text disappears.
<div onClick={toggleButton()}>
<i
role="toggle button"
aria-pressed="true"
alt="Toggle text"
class="material-icons"
>
toggle_off
</i>
Random text...
</div>
<div onClick={toggleButton()}>
<i
role="toggle button"
aria-pressed="true"
alt="Toggle text"
class="material-icons"
>
toggle_on
</i>
</div>
-an if conditional would render either of these divs based on pressed or not pressed
Typically, I handle this button adding a role, aria-state, and alt text to the material icon and change the aria-state when clicked. However, I always feel that I might not be doing this as effectively as I should.
What is the proper way to make something like an icon used as a toggle button accessible?
(I know that best practice for WCAG (web accessibility) is to use a button component, but because of the unique nature of material icons that's not possible.)
You'll need to change a few things:
role="toggle button" should be role="button"
Beginning with aria-pressed="true" is fine if your default button state is pressed. Otherwise, its initial value should be false
Get rid of alt, as it doesn't belong here. If you're including text in your icon (as in your example code), you don't need to replace anything. Otherwise, use aria-label and put your button text there
Add tabindex="0" so that your icon can be reached by keyboard
Event handling
To make your icon behave like a real button, you'll also need to listen for keys like the space-bar and enter key. These keystrokes should be treated as clicks, as a real button would.
const el = document.querySelector('my-icon');
el.addEventListener('keypress', handleKeyPress);
el.addEventListener('click', handleClick);
function handleClick() {
// Update aria-pressed
}
function handleKeyPress(e) {
// Handle space-bar and enter key
}
So, in the end, your toggle icon button might look something like this:
<i
role="button"
aria-pressed="false"
class="material-icons"
>
Button text
</i>
or (sans visible button text):
<i
role="button"
aria-pressed="false"
aria-label="Button text"
class="material-icons"
></i>

Rails 4: link_to broken in Bootstrap context

So I am following the Twitter Bootstrap documentation and am trying to implement a simple dropdown <a> link with some extra attributes thrown at it in my .navbar (cf. http://getbootstrap.com/components/#navbar-default)
The line in the documentation is:
Dropdown <span class="caret"></span>
Which I translated in Rails as :
=link_to who_we_are_path, class:'dropdown-toggle', role:'button', 'aria-expanded' =>'false', data:{toggle: 'dropdown'} do
Who We Are
%span.caret
Which generates the following HTML:
<a aria-expanded="false" class="dropdown=toggle" data-toggle="dropdown" href="/who_we_are" role"button">
"Who We Are"
<span class="caret"></span>
</a>
Which looks totally valid. However, when I click the text "Who We Are" in the DOM, it does not actually go to the /who_we_are path, however when I do a mouse hover over the text I see in the bottom left of my browser I see 0.0.0.0:3000/who_we_are.
That's Because, Role of dropdown is to load Menu Item, Bootstrap prevents Defaults for dropdowns and Shows up a menu. You should add your link inside the Menu or you have to Override Bootstraps defaults.
see http://getbootstrap.com/components/#dropdowns
Try this one
%div.dropdown
=link_to "#", class:'dropdown-toggle', role:'button', 'aria-expanded' =>'false', data:{toggle: 'dropdown'} do
Load Menu
%span.caret
%ul.dropdown-menu{role:"menu"}
%li=link_to "Who We Are", who_we_are_path