keydown and keypress events not working in angular - angularjs

I have a directive where I am trying to bind the keypress and keydown events but for some reason they will not register.
element.bind("keydown keypress", function (event) {
console.log("here");
});
Any clues why this could be ?
If you look at the file selectMe.js in this plnkr you can see the binding.

Try adding tabindex="0" to the <table>. With this you make the <table> focusable with a click or pressing tab. However you may be interested on starting listening to key events as soon as the page is loaded. Then you should bind the event to document:
angular.element(document).bind(...)

If you bind to the table element, you won't get key events because the table doesn't have focus. See this question for info on which elements can have focus.
The events do bubble up however, so you can either add an invisible input or anchor element and get the keyboard events when they bubble up, or just listen on your document element to get any unhandled events. You can also check the ctrlKey flag on the key event instead of tracking the control key yourself:
var doc = angular.element(document);
doc.on('keyup', function(e) {
if (e.ctrlKey) {
if (e.keyCode === cKey) {
$scope.valueToBeCopied = e.target.value;
console.log('copy: valueToBeCopied: ' + $scope.valueToBeCopied);
}
if (e.keyCode === vKey) {
e.target.value = $scope.valueToBeCopied;
console.log('paste: valueToBeCopied: ' + $scope.valueToBeCopied);
}
}
});
I don't know how much that helps though with what you seem to be attempting. You'll have to track which element has the virtual 'focus' some other way.

Related

How can I prevent my custom directive from getting compiled/executed every time I use $route.reload()

I have built a custom directive to enable arrow key navigation in a dropdown.
This is my HTML code
<div ng-click="dropdownShow = !dropdownShow" id="dropdownToggle" arrow-navigation>
{{currentlySelectedItem}}
</div>
<div ng-show="dropdownShow">
<div ng-repeat="item in list" id="row_{{$index}}" ng-click="getItemInfo($index)">
<span>{{item}}</span>
</div>
</div>
And my JS code
app.directive('arrowNavigation', ['$document', function($document){
return{
restrict: 'A',
link: function(scope, element, attrs){
$document.bind('keydown',function(e){
// check if dropdown open
if(scope.dropdownShow){
// if down arrow key pressed
if(e.keyCode === 40){
console.log("down arrow key pressed");
// When the dropdown is first opened, the focus will be on the dropdownToggle.
// In this case, I'm moving the focus to the first item on the list.
if(document.activeElement.id === "dropdownToggle"){
document.getElementById('row_0').focus();
}
else{
let currentFocused = document.activeElement.id;
// currentFocused = row_ + $index
let index = currentFocused.substring(4);
// index = $index of currently focused item
console.log(index);
index++;
// check if the currently focused item is the last item on the list
// In this case, move the focus back to the first item on the list
if(index >= scope.list.length){
document.getElementById('row_0').focus();
}
else{
document.getElementById('row_' + index).focus();
}
}
e.preventDefault();
}
// there's similar code for up arrow key press. I have decided to skip it for the sake of simplicity.
}
})
}
}
}])
The first time I use the dropdown, everything works perfectly.
But when I select any item from the dropdown, the resulting ng-click function has a $route.reload inside it. This causes my ng-view to get reloaded. That's when the problem starts. After the first reload, when I try to use the dropdown, it gets executed twice for every single arrow click. So if the first list item is focused, and I press the down arrow key, instead of moving the focus to the second item, it moves the focus to the third item. Upon every subsequent $route.reload(), the number of executions increases by one.
I'm guessing that this is happening cause everytime the route gets reloaded, the directive is being re-rendered, causing multiple copies of the same directive, all of which then get executed on the arrow click.
Is there any way to prevent this re-rendering?
Remove the event listener when scope is destroyed
// inside link
scope.$on("$destroy", function() {
$document.unbind('keydown')
});
Note that angular.element bind and unbind are deprecated so am assuming you may be using a fairly old version. Most recent versions use on() and off()

Trigger other key rather than original key using angularjs

I am try to trigger " ` " keyCode = 192 when I press " enter " keyCode = 13. I am trying below code but it is not working in contenteditable div. Any idea?
And how to $apply this?
$scope.pressKey = function(event){
if(event.keyCode == 13){
event.preventDefault();
var e=angular.element.Event('keydown');
console.log(e);
e.which=192;
$('#regularDot').trigger(e);
}
<div id="regularDot" ng-keypress="pressKey($event)" class="wf-loading"
ng-model="regularDot" contenteditable="true" n></div>
Don't use pure JS methods like KeyboardEvent() it has cross browser support issues, use jQuery instead.
Now angular.element has basic support of jquery but it does not support Event() method which you used. So best approach is having jquery library in your codebase (with its script tag attached before angularjs in index.html). Then change your code to as follows:
$scope.pressKey = function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var e = jQuery.Event('keypress');
console.log(e);
e.which = 192;
e.keyCode = 192;
$timeout(function() {
$('#regularDot').trigger(e);
});
} else if(event.keyCode == 192){
console.log("Newly triggered event catched with keyCode " + event.keyCode);
}
}
Here I've made just few changes like adding trigger method inside $timeout so that it'll wait small time to complete the preventDefault of previously triggered event before setting (triggering) new event, and doing $apply also. I've changed newly triggered event type to keypress instead of keydown so that after triggering event you'll be able to handle it using ng-keypress on that element (with id regularDot).
Similar plunker example to refer.

Easily Catch Enter to Submit Form

Is there an easy way to enable hitting enter to execute some javascript for a form with paper-input's. I can catch the keystroke on enter for every element but this seems kind of tedious.
With the current Polymer version 1.0 I was able to resolve that using iron-a11y-keys.
Here is an example bound to the whole form which triggers submission on any child input element:
<iron-a11y-keys id="a11y" target="[[_form]]" keys="enter"
on-keys-pressed="submitForm"></iron-a11y-keys>
<form is="iron-form" id="form"
method="post"
action="{{url}}">
...
Polymer({
is: 'example-form',
properties: {
_form: {
type: Object,
value: function() {
return this.$.form;
}
}
},
submitForm: function() {
document.getElementById('form').submit();
},
Currently (Polymer 0.3.4) there seems to be no event fired when one presses the enter key in a paper-input. But you can extend the paper-input element and add this functionality (see Extending other elements in the Polymer doc):
<polymer-element name="my-paper-input" extends="paper-input">
<template>
<shadow></shadow>
</template>
...
</polymer-element>
Then you can fire a custom event when the return key is pressed:
ready: function() {
self = this;
this.$.input.addEventListener('keypress', function(e) {
if (e.keyCode == 13) {
self.async(function() {
self.fire('enter', self.value);
});
}
});
}
For convenience the input value is passed to the event handler. Now you can use your new element like so:
<my-paper-input ... on-enter="{{inputEntered}}"></my-paper-input>
Edit 1:
Since the event bubbles up in the element hierarchy, one can catch it on the surrounding form element:
<my-form on-enter="{{anyInputEntered}}" ...>
Then one gets the events of all input elements in one place (the event propagation can be stopped by calling stopPropagation(); on the event object).
Edit 2:
It's best to give custom events unique names, so that they don't clash with the names of core events that may be added in the future (e.g. my-unique-prefix-input-entered).

In Ext.js4.1.3, how to find a combobox related to its internal item?

I have a ComboBox subclass which has a customized template for rendering its data. Once one internal list item is clicked, there is a listener to the browser "click" event, which needs to make some changes on the original combobox.
How can I find a reference to its related combobox from this listener?
This is what I found, which answers my question.
From the "click" listener 2nd argument, we get the click event target element. We can then navigate to its parent boundlist element, using the .x-boundlist class, get its component and finally reach the combobox through its not-documented pickerField property or its getBubbleTarget() method.
click:{
element:'el',
fn: function(ev, target) { //listener
var el= Ext.get(target),
boundList_el, boundList, combobox;
boundList_el= el && el.findParentNode(".x-boundlist");
boundList= boundList_el && Ext.getCmp(boundList_el.id);
combobox= boundList && boundList.getBubbleParent();
//Do something with the combobox, like changing its value
}
Since the customized combo contents was defined using listConfig and its click listener was defined through the listeners config, this refers to the boundList element in the scope of the listener. A more simple way to reach the same result:
click:{
element:'el',
fn: function(ev, target) { //listener
var boundList= Ext.getCmp(this.id),
combobox= boundList && boundList.getBubbleParent();
//Do something with the combobox, like changing its value
}

Why is my click event called twice in jquery?

Why is my click event fired twice in jquery?
HTML
<ul class=submenu>
<li><label for=toggle><input id=toggle type=checkbox checked>Show</label></li>
</ul>
Javascript
$("ul.submenu li:contains('Show')").on("click", function(e) {
console.log("toggle");
if ($(this).find("[type=checkbox]").is(":checked")) console.log("Show");
else console.log("Hide");
});
This is what I get in console:
toggle menu.js:39
Show menu.js:40
toggle menu.js:39
Hide menu.js:41
> $("ul.submenu li:contains('Show')")
[<li>​ ]
<label for=​"toggle">​
<input id=​"toggle" type=​"checkbox" checked>​
"Show"
</label>​
</li>​
If I remember correctly, I've seen this behavior on at least some browsers, where clicking the label both triggers a click on the label and on the input.
So if you ignore the events where e.target.tagName is "LABEL", you'll just get the one event. At least, that's what I get in my tests:
Example with both events | Source
Example filtering out the e.target.tagName = "LABEL" ones | Source
I recommend you use the change event on the input[type="checkbox"] which will only be triggered once. So as a solution to the above problem you might do the following:
$("#toggle").on("change", function(e) {
if ($(this).is(":checked"))
console.log("toggle: Show");
else
console.log("toggle: Hide");
});
https://jsfiddle.net/ssrboq3w/
The vanilla JS version using querySelector which isn't compatible with older versions of IE:
document.querySelector('#toggle').addEventListener('change',function(){
if(this.checked)
console.log('toggle: Show');
else
console.log('toggle: Hide');
});
https://jsfiddle.net/rp6vsyh6/
This behavior occurs when the input tag is structured within the label tag:
<label for="toggle"><input id="toggle" type="checkbox" checked>Show</label>
If the input checkbox is placed outside label, with the use of the id and for attributes, the multiple firing of the click event will not occur:
<label for="toggle">Show</label>
<input id="toggle" type="checkbox" checked>
I found that when I had the click (or change) event defined in a location in the code that was called multiple times, this issue occurred. Move definition to click event to document ready and you should be all set.
Not sure why this wasn't mentioned. But if:
You don't want to move the input outside of the label (possibly because you don't want to alter the HTML).
Checking by e.target.tagName or even e.target doesn't work for
you because you have other elements inside the label
(in my case it had spans holding an SVG with a path so e.target.tagName sometimes showed SVG and other times it showed PATH).
You want the click handler to stay on the li (possibly because you have
other items in the li besides the checkbox).
Then this should do the trick nicely.
$('label').on('click', function(e) {
e.stopPropagation();
});
$('#toggle').on('click', function(e) {
e.stopPropagation();
$(this).closest('li').trigger('click');
});
Then you can write your own li click handler without worrying about events being triggered twice. Personally, I prefer to use a data-selected attribute that changes from false to true and vice versa each time the li is clicked instead of relying on the input's value:
$('ul.submenu li').on('click', function() {
let _li = $(this),
ticked = _li.attr('data-selected');
ticked = (ticked === 'false') ? true : false;
_li.attr('data-selected', ticked);
_li.find('#toggle').prop('checked', ticked);
});

Resources