How to make PrimeNG Dropdown Keyboard Accessible - accessibility

I want to make dropdown keyboard accessible. Right now, its not working when i am using keyboard up and down arrow. I applied tabindex but still not working. Anyone have any idea about this..
<p-dropdown [options]="cities" tabindex="1" placeholder="Select an option"></p-dropdown

I tried this it working properly using keyboard up and down arrow :
npm version :
"primeng": "^4.1.3"
in html file
<p-dropdown [options]="cities" [(ngModel)]="selectedCity" tabindex="1" placeholder="Select an option"></p-dropdown>
module.ts
import { DropdownModule } from 'primeng/primeng';
#NgModule({
imports: [
DropdownModule
]
})
component.ts
import {SelectItem} from 'primeng/primeng';
cities: SelectItem[];
selectedCity: string;

PrimeNG not support reading dropdown options by default. Default behavior is navigation by arrows and change value. Default tabulator key action is hide overlay with element items. My solution forces tabulator key to choose dropdown option and when you hit enter key the value is changed.
I prepared angular directive which override default dropdown behavior. You can add add this directive do module and import it in place where you want use this change.
Testing for PrimeNG 8.0.0:
#Directive({
selector: 'p-dropdown',
})
export class DropdownDirective implements OnInit, OnDestroy {
readonly KEY_DOWN_EVENT: string = 'keydown';
readonly FOCUS_IN_EVENT: string = 'focusin';
readonly TABINDEX_ATTRIBUTE: string = 'tabindex';
readonly LIST_ITEM_SELECTOR: string = 'li';
private focusInSubscription: Subscription = new Subscription();
private subscriptions: Subscription = new Subscription();
private listElementSubscriptions: Subscription[] = [];
private readonly dropdownHtmlElement: HTMLElement;
constructor(private dropdown: Dropdown,
private elementRef: ElementRef) {
this.dropdownHtmlElement = this.elementRef.nativeElement as HTMLElement;
}
ngOnInit(): void {
this.replaceKeyDownAction();
this.subscribeToDropdownShowEvent();
this.subscribeToDropdownHideEvent();
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
private subscribeToDropdownShowEvent() {
this.subscriptions.add(
this.dropdown.onShow.subscribe(() => {
this.updateElementsList();
this.subscribeToFocusInEvent();
}),
);
}
private subscribeToDropdownHideEvent() {
this.subscriptions.add(
this.dropdown.onHide.subscribe(() => {
this.unsubscribeFromFocusInEvent();
this.unsubscribeFromListElementsKeyDownEvents();
}),
);
}
private updateElementsList() {
const listElements = this.dropdownHtmlElement.querySelectorAll<HTMLLIElement>(this.LIST_ITEM_SELECTOR);
listElements.forEach((listElement: HTMLLIElement) => {
this.subscribeToListElementKeyDownEvent(listElement);
listElement.setAttribute(this.TABINDEX_ATTRIBUTE, '0');
});
}
private subscribeToListElementKeyDownEvent(listElement: HTMLLIElement) {
this.listElementSubscriptions.push(
fromEvent(listElement, this.KEY_DOWN_EVENT)
.pipe(filter((event: KeyboardEvent) => event.key === KEYBOARD_KEY.ENTER))
.subscribe(() => {
// Simulation of mouse click of list element (trigger with (click) event in p-dropdownItem component which is child element of p-dropdown)
listElement.click();
}),
);
}
private unsubscribeFromListElementsKeyDownEvents() {
this.listElementSubscriptions.forEach((singleSubscription: Subscription) => singleSubscription.unsubscribe());
this.listElementSubscriptions = [];
}
private subscribeToFocusInEvent() {
this.focusInSubscription = fromEvent(document, this.FOCUS_IN_EVENT).subscribe(({target}) => {
// Situation when focus element is outside dropdown component
if (!this.dropdownHtmlElement.contains(target as HTMLElement)) {
this.dropdown.hide();
}
});
}
private unsubscribeFromFocusInEvent() {
this.focusInSubscription.unsubscribe();
}
/**
* Overwrite default onKeydown method from PrimeNG dropdown component
*/
private replaceKeyDownAction() {
const onKeyDownOriginFn = this.dropdown.onKeydown.bind(this.dropdown);
this.dropdown.onKeydown = (event: KeyboardEvent, search: boolean) => {
if (event.which === 9) {
// Napisuję domyślne zachowanie tabulatora zdefiniowanego w klasie komponentu Dropdown z biblioteki PrimeNG
} else {
onKeyDownOriginFn(event, search);
}
}
}
}

Related

How do I make my boolean column in ag-grid show checkboxes

I have a boolean column in my grid which is currently displaying 'true' or 'false' but I want it to show a checkbox instead.
How should I do this.
We are using ag-grid 25 with Angular and Adaptable.
You can write your own cell renderer that renders a checkbox instead of a string. Below is an example:
import { Component, OnDestroy } from '#angular/core';
import { ICellRendererAngularComp } from '#ag-grid-community/angular';
#Component({
selector: 'checkbox-renderer',
template: `
<input
type="checkbox"
(click)="checkedHandler($event)"
[checked]="params.value"
/>
`,
})
export class CheckboxRenderer implements ICellRendererAngularComp, OnDestroy {
private params: any;
agInit(params: any): void {
this.params = params;
}
checkedHandler(event) {
let checked = event.target.checked;
let colId = this.params.column.colId;
this.params.node.setDataValue(colId, checked);
}
}
import { CheckboxRenderer } from "./checkbox-renderer.component";
this.frameworkComponents = {
checkboxRenderer: CheckboxRenderer
};
Live Demo
Resource
https://blog.ag-grid.com/binding-boolean-values-to-checkboxes-in-ag-grid
As you say that you are using AdapTable then you can just add the name of the column to the CheckBoxColumns list in PredefinedConfig / UserInterface
See: https://docs.adaptabletools.com/docs/predefined-config/user-interface-config#checkboxcolumns
So something like:
export default {
UserInterface: {
CheckboxColumns: ['myBooleanColumn'],
},
} as PredefinedConfig;
That will dynamically create a CellRenderer very similar to the one which NearHuscarl suggests.
If the column is ReadOnly the checkboxes will be disabled.
Its worth noting that the CheckboxColumnClickedEvent will fire each time a cell in the column is clicked.

Set Angular HostBinding css class to value with function?

I have a component that uses #HostBinding to set a class:
#HostBinding('class.dark-1') true;
Which works fine. However, now I need to create a function in my component to change the class dynamically.
For example, from dark-1 to light-2 when a button in the component is clicked.
I know how to create the function and call it from a button, but how do I change the class in the hostbinding and refresh the UI with the new class?
You can toggle a clicked flag when clicking the button, and set the classes with getters:
#HostBinding("class.dark-1") public get classDark1() {
return !this.clicked;
}
#HostBinding("class.light-2") public get classLight2() {
return this.clicked;
}
private clicked = false;
public onClick() {
this.clicked = true;
}
Sinply give it a property name:
#HostBinding('class.dark-1') isDark = true;
Then you can change it:
this.isDark = false;
Or change entire className:
#HostBinding('class') className = 'dark-1';
this.className = 'light-1';

Ionic2 view doesn't bind with observable

I have the following component class that receives an observable from a service. I want my component to unsubscribe when destroyed. This is the code is working and the view gets updated (the view renders different content depending on the value of state with *ngIf):
#Component({
templateUrl: 'my-widget.component.html',
selector: 'my-widget'
})
export class MyWidgetComponent implements OnInit, OnDestroy {
// Enum type variable to keep state
state: MyWidgetStateType;
// This is needed to be able to use the enum type in the template
private myWidgetStateType = MyWidgetStateType;
// Keep a reference to the observable to unsubscribe from it
private sub: Subscriber<any>;
constructor(public myWidgetService: MyWidgetService) {
}
ngOnInit(): void {
this.sub = Subscriber.create((state: MyWidgetStateType) => {
console.log('Widget state: ' + MyWidgetStateType[state]);
this.state = state;
});
this.myWidgetService.getState().subscribe(this.sub);
}
ngOnDestroy(): void {
// Unsubscribe here
this.sub.unsubscribe();
}
}
The state type is an enum with 3 states:
export enum MyWidgetStateType {
Enabled,
Dormant,
Locked
};
And the view my-widget.component.html:
<h2>My Widget</h2>
<div *ngIf="state === myWidgetStateType.Enabled">
<p>Enabled</p>
</div>
<div *ngIf="state === myWidgetStateType.Dormant">
<p>Dormant</p>
</div>
<div *ngIf="state === myWidgetStateType.Locked">
<p>Locked</p>
</div>
However, if I try to extract the call in the subscriber to a private function, the console.log method works but the view doesn't render any content according to the state (the <h2> title is rendered but there is no <div> element):
#Component({
templateUrl: 'my-widget.component.html',
selector: 'my-widget'
})
export class MyWidgetComponent implements OnInit, OnDestroy {
// Enum type variable to keep state
state: MyWidgetStateType;
// This is needed to be able to use the enum type in the template
private myWidgetStateType = MyWidgetStateType;
// Keep a reference to the observable to unsubscribe from it
private sub: Subscriber<any>;
constructor(public myWidgetService: MyWidgetService) {
}
ngOnInit(): void {
this.sub = Subscriber.create(this.onStateChange);
this.myWidgetService.getState().subscribe(this.sub);
}
ngOnDestroy(): void {
// Unsubscribe here
this.sub.unsubscribe();
}
private onStateChange(state: MyWidgetStateType): void {
console.log('Widget state: ' + MyWidgetStateType[state]);
this.state = state;
}
}
The question is: why if the onStateChange function gets called, the view never gets the value update?
Of course this is not blocking my work because I have the solution above, but I'm just curious.

Remove by order TitleWindows in Flex

I create 3 TitleWindow:
PopUpManager.addPopUp( TitleWindow1, root, false);
PopUpManager.addPopUp( TitleWindow2, root, false);
PopUpManager.addPopUp( TitleWindow3, root, false );
And then I manage those position and depth using mouse. Now I want to delete them by depth.
In my TitleWindow add listener:
root.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
private function onKeyDown(event:KeyboardEvent = null):void {
if ( event.keyCode == Keyboard.ESCAPE && this. ) { //!!!! how to detect this window depth, that it at first plan??
event.preventDefault();
event.stopImmediatePropagation();
closeHandler();
}
}
}
How does me solve this issue?
Now, I can offer one idea, I use state inactive:
private function onKeyDown(event:KeyboardEvent = null):void {
if ( event.keyCode == Keyboard.ESCAPE
&& this.getCurrentSkinState() != "inactive" ) {
event.preventDefault();
event.stopImmediatePropagation();
closeHandler();
}
}
override protected function stateChanged (oldState:String, newState:String, recursive:Boolean) : void {
super.stateChanged( oldState, newState, recursive );
if ( oldState == "inactive" && newState == "normal" ) {
PopUpManager.bringToFront( this );
}
}
You'll need to keep track of this when you add the pop ups. Here is an example class that extends the pop up manager:
package
{
import flash.display.DisplayObject;
import mx.collections.ArrayCollection;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModuleFactory;
import mx.core.UIComponent;
import mx.managers.PopUpManager;
public class PopManagerDepth extends PopUpManager
{
public function PopManagerDepth() { super(); }
public static var popUpsByDepth :ArrayCollection = new ArrayCollection();
public static function addPopUpWithDepth(window:IFlexDisplayObject,
parent:DisplayObject,
modal:Boolean = false,
childList:String = null,
moduleFactory:IFlexModuleFactory = null):void
{
PopUpManager.addPopUp(window, parent, modal, childList, moduleFactory);
PopManagerDepth.popUpsByDepth.addItem( window );
}
public static function removeLastPopUp():void{
PopManagerDepth.removePopUpByIndex(PopManagerDepth.popUpsByDepth.length);
}
public static function removePopUpByIndex( idx :uint ):void{
if( PopManagerDepth.popUpsByDepth.length > idx){
PopUpManager.removePopUp( popUpsByDepth.getItemAt( idx ) as IFlexDisplayObject );
}
}
}
}
You'd use this class to add your popups, just like popupmanager but with this method:
PopManagerDepth.addPopUpWithDepth( .... );
The difference is there is a new method you'd use to remove them :
PopManagerDepth.removeLastPopUp();
or
PopManagerDepth.removePopUpByIndex();
Should do the trick, sorry I cant test it right now, but should work or close to it. I'll help ya debug if needed :)
By Windows depth do you mean the z-index ? If so you might want to try the getChildIndex and removeChildAt methods on the DisplayObject class. However, since PopUps exists in a different heirarchy than the rest of the general components, I am unsure if this will work but it is a start ?
Here's what I would do:
Create an Array to keep track of each TitleWindow instance you pop up. You can use the Array index as a z-index indicator.
In your closeHandler() method, iterate through each member of the Array and close that TitleWindow instance.

Customize the alert box with two buttons

I need to customize my alert box with two button and both the button need to have different skins on them.
I was able to do it for one skin. But i am unable to figure out about the two skins.
My code that i have right now is below:
public function Messages():void
{
Alert.buttonWidth = 100;
Alert.yesLabel = "Accept";
Alert.noLabel = "Reject";
var alert:Alert = Alert.show("Accept video chat request from "+far_alias+"?", "Incoming Video Chat Request", Alert.YES | Alert.NO | Alert.NONMODAL, Sprite(Application.application), handleIncomingCallResponse, null, Alert.YES);
alert.data = cm;
alert.name = alert_name;
alert.styleName = "requestVideoAlert"
_alertDic[alert_name] = alert;
Alert.yesLabel = "Yes";
Alert.noLabel = "No";
}
And the Css code is below:
<mx:Style>
.requestVideoAlert
{
background-color: #000000;
back-color: #000000;
area-fill:#000000;
border-color:#000000;
dropShadowEnabled: false;
button-style-name: cancelbtn;
}
.cancelbtn
{
skin: Embed(source='assets/images/videoChat-cancel-button.png');
}
This give the same skin on both the buttons "Accept" and "Reject"
Can some one help me with this.
Thank you
Zee
I dont know if you already found an solution but if no and you use flex 3 I can help you.
This code should do what you need.
Changing buttons and even changing style for text on buttons.
'
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
initStyles();
var myAlert:Alert = Alert.show("description", "title", Alert.YES | Alert.NO);
var arrOfButtons:Array = myAlert.mx_internal::alertForm.mx_internal::buttons;
var button1:Button = arrOfButtons[0];
var button2:Button = arrOfButtons[1];
// buttons filters
button1.styleName = 'buttonStyle1';
button2.styleName = 'buttonStyle2';
button1.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(event:Event):void
{
// you can change text on buttons as well
var btn:Button = event.target as Button;
var text:UITextField = btn.getChildAt(0) as UITextField;
text.filters = [ new GlowFilter(0x3946C0, 1, 4, 2, 8)];
}
/**
* set style of remove button for alert
**/
private function initButton1Style():void
{
var buttonStyle1:CSSStyleDeclaration = new CSSStyleDeclaration('buttonStyle1');
buttonStyle1.setStyle('fontWeight', "normal");
buttonStyle1.setStyle('fontColor', 0x000000);
buttonStyle1.setStyle('color', 0x000000);
buttonStyle1.setStyle("fillColors", [ 0xffffff, 0xF5A2A2, 0xF5A2A2, 0xffffff ]);
buttonStyle1.setStyle('fontSize', 10);
buttonStyle1.setStyle('themeColor', 0xff0000);
StyleManager.setStyleDeclaration(".buttonStyle1", buttonStyle1, true);
}
/**
* set style of buy button for alert
**/
private function initButton2Style():void
{
var buttonStyle2:CSSStyleDeclaration = new CSSStyleDeclaration('buttonStyle2');
buttonStyle2.setStyle('fontWeight', "normal");
buttonStyle2.setStyle('fontColor', 0x000000);
buttonStyle2.setStyle('color', 0x000000);
buttonStyle2.setStyle("fillColors", [ 0xffffff, 0xBAFFAB, 0xBAFFAB, 0xffffff ]);
buttonStyle2.setStyle('fontSize', 10);
buttonStyle2.setStyle('themeColor', 0x7CCB6C);
StyleManager.setStyleDeclaration(".buttonStyle2", buttonStyle2, true);
}
private function initStyles():void
{
initButton1Style();
initButton2Style();
}
]]>
</mx:Script>
'
Unfortunately, I don't think there's a simple way of doing this - the button styles are set in AlertForm by looping through all the buttons on the Alert and setting their stylename to buttonStyleName. In order to set seperate button styles, I think you'd have to extend both Alert (to use a custom AlertForm class) and AlertForm (to override styleChanged to assign seperate style names).
Try this:
package utils
{
import flash.events.EventDispatcher;
import mx.controls.Alert;
import mx.controls.Button;
import mx.core.mx_internal;
public class AlertUtility extends EventDispatcher
{
use namespace mx_internal;
public static function getYesNoAlert(title:String, message:String,closeFunction:Function):void{
var myAlert:Alert = Alert.show(message, title, Alert.YES | Alert.NO,null,closeFunction,null,Alert.NO);
var arrOfButtons:Array = myAlert.mx_internal::alertForm.mx_internal::buttons;
var button1:Button = arrOfButtons[0];
var button2:Button = arrOfButtons[1];
button1.styleName = 'alertBtnStyle1';
button2.styleName = 'alertBtnStyle2';
}
}
}
and in your style css:
mx|Button.alertBtnStyle1 {
skin: ClassReference("skins.myEmphasizedSkin");
/* desired style */
}
mx|Button.alertBtnStyle2 {
emphasizedSkin: ClassReference("skins.myButtonDefaultSkin");
/* desired style */
}

Resources