jQuery event trigger is not working on annotorious and seadragon - javascript

I am trying to get the down arrow keyup event to fire automagically using jQuery. The annotorious/seadragon combination has a listener that opens all preconfigured tags when I press the down arrow.
I have written jQuery code to find the input field, put focus on it and then trigger the keyup event.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keyup");
event.keyCode = event.which = 40; // down arrow
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
} // if
})
} // triggerDownArrowOnInput
The focus is working great, but not the trigger. If I manually hit the down arrow key, then the preconfigured tags all appear:
I have tried "keyCode" and "which" separately.
I have tried triggering $(this).keyup(event).
I have tried putting in a delay between the focus call and the trigger/keyup call.
I have tried calling $(document).trigger(event).
I thought maybe I was sending the event to the wrong element, but it appears (going through Dev tools) that only the Input field and the document have the listeners enabled.
No matter what I do, I can't get the event to fire. Any ideas?
Thanks.

I think I've got this working without jQuery, using a KeyboardEvent and dispatchEvent. With my tests I don't think you need the focus before hand either because it's an event on the element, but worth testing this on your application.
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
this.dispatchEvent(new KeyboardEvent('keyup',{'keyCode': 40, 'key':'ArrowDown', 'code':'ArrowDown'}));
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
}

Have you tried keydown?
var e = jQuery.Event("keydown");
e.which = 40;
e.keyCode = 40
$(this).trigger(e);
function triggerDownArrowOnInput() {
$("[id^=downshift][id$=input]").each(function(index) {
// There should only be 1, but let's not assume.
console.log(index);
if (index == 0) {
console.log("Found an input: " + $(this).attr("id"))
$(this).focus();
var event = jQuery.Event("keydown");
event.keyCode = event.which = 40;
$(this).trigger(event);
} else {
console.log("Multiple elements found that match the id: " + $(this).attr("id"));
}
})
} // triggerDownArrowOnInput

I was able to get the event to fire, but still wasn't able to open the menu on focus. I ended up having to create a development environment for:
recogito/recogito-client-core
recogito/recogito-js
recogito/annotorious
recogito/annotorious-openseadragon
I then modified Autocomplete.jsx in recogito/recogito-client-core, added an OnFocus listener and then added the following code:
const onFocus = evt => {
if (!isOpen) {
this.setState({ inputItems: this.props.vocabulary }); // Show all options on focus
openMenu()
} // if
} // onFocus
Way more than I wanted to do, but it is working now.

Related

Why is the focus handler getting executed with the wrong parameter?

I have a focus handler on a textfield:
$("#input").on("focus", (e) => {
// do some stuff
});
When I right-click, however, I don't want that focus handler to be executed, so I did:
$("#input").on("mousedown", (e) => {
if (e.button === 2) { // right click
e.preventDefault();
}
});
However, that also prevents the textfield from ever getting focus when I right-click. I still want it to get focus, I just don't want the handler to execute, so I triggered the handler manually:
$("#input").on("mousedown", (e) => {
if (e.button === 2) { // right click
e.preventDefault();
$("#input").trigger("focus", true);
}
});
$("input").on("focus", (e, someParam) => {
if (someParam) return;
// do some stuff
});
This way, the textfield gets focus, but we immediately return out of the handler.
The problem I noticed is that the first time I trigger the focus handler, someParam is undefined and we end up executing do some stuff. For all subsequent right-clicks, someParam is true.
I commented out the line that triggers the focus handler, and indeed, the focus handler is never executed, because we call preventDefault, so it seems that the first execution of the handler necessarily comes from $("#input").trigger("focus", true);. So why then is someParam undefined if I'm passing in true as the extra parameter?
JsFiddle. Tested in Chrome.
This appears to be a current issue with jQuery. See this github issue.
As a workaround, try the following:
var a = $("#a");
var _focusData = null;
var focusEvent = (e) => {
if (_focusData) {
_focusData = null;
return;
}
_focusData = null;
var t = $("textarea").first();
t.val(t.val() + "\nfocus");
};
a.on("mousedown", (e) => {
if (e.button === 2) {
e.preventDefault();
var t = $("textarea").first();
t.val(t.val() + "\n" + e.button);
_focusData = true;
a.trigger("focus");
}
});
a.on("focus", focusEvent);
After doing a lot more research, including trying to trigger custom events with $.Event, it seems like your best course of action is to either use stack traces, pollute the global scope, or downgrade your jQuery version.
I found another solution besides the comment from CBroe (to just perform the logic in an else statement):
Use a named function as our mouse down handler, then examine the stack trace.
var a = $("#a");
a.on("mousedown", onMouseDown);
function onMouseDown(e) {
if (e.button === 2) {
e.preventDefault();
e.stopImmediatePropagation()
var t = $("textarea").first();
t.val(t.val() + "\n" + e.button);
a.trigger("focus", true);
}
}
a.on("focus", (e, someParam) => {
var stackTrace = getStackTrace();
if(stackTrace.indexOf("onMouseDown") >= 0) return;
var t = $("textarea").first();
t.val(t.val() + "\nfocus");
console.log(someParam);
console.trace();
});
var getStackTrace = function() {
var obj = {};
if(Error.captureStackTrace) { //Chrome (IE/Edge? Didn't test)
Error.captureStackTrace(obj, getStackTrace);
}
else { //Firefox
obj = Error();
}
return obj.stack;
};
https://jsfiddle.net/bjj56eua/4/
As I was typing this up, FrankerZ posted an answer which looks much nicer. I suggest doing that. This was a dirty hack involving string parsing, but it works. It just isn't a good idea.

Keypress in backbone.js?

It looks like key-press can only be executed on a focus element? I don't fully buy into that, there has to be a way to execute a key-press event similar to a click event?
I have a view that works with one item at a time. I have a mouseenter - mouseleave function that adds a class to the item the mouse is over. When the item receives that class I want to be able to use a key-press event to run a function on that item.
Obviously this is a slight obstacle but Id like to find out what I need to do. Below is an example view.
var PlayerView = Backbone.View.extend({
tagName: 'div',
events: {
'click .points, .assists, span.rebounds, span.steals':'addStat',
'mouseenter': 'enter',
'mouseleave': 'leave',
'keypress': 'keyAction'
},
enter: function() {
this.$el.addClass('hover');
},
leave: function() {
this.$el.removeClass('hover');
},
keyAction: function(e) {
var code = e.keyCode || e.which;
if(code == 65) {
alert('add assist')
}
}
});
So there isn't much logic here, but I am thinking I would write something like this
keyAction: function(e) {
var code = e.keyCode || e.which;
if(code == 65) {
var addAssist = parseInt(this.model.get('assists')) + 1;
this.model.save({assists: addAssist});
}
}
Basically If I could figure out how to fire that keyAction method I should be good to go. So what are some caveats I am missing in executing some code like this? I am sure there are a few.
I do understand some of what is wrong with this code, it has no way of knowing when we run keypress in that view, I would have to add a conditional or something to find the active class, so when I execute the keypress it knows what model I am talking about, very vague description here but I get there is something wrong I am just not sure how to do this?
My solution
initialize: function() {
this.listenTo(this.model, "change", this.render);
_.bindAll(this, 'on_keypress');
$(document).bind('keydown', this.on_keypress);
},
enter: function(e) {
this.$el.addClass('hover');
},
leave: function(e) {
this.$el.removeClass('hover');
},
on_keypress: function(e) {
// A for assist
if(e.keyCode == 65) {
if(this.$el.hasClass('hover')) {
var addThis = parseInt(this.model.get('assists')) + 1;
this.model.save({assists: addThis});
}
}
// R for rebound
if(e.keyCode == 82) {
if(this.$el.hasClass('hover')) {
var addThis = parseInt(this.model.get('rebounds')) + 1;
this.model.save({rebounds: addThis});
}
}
// S for steal
if(e.keyCode == 83) {
if(this.$el.hasClass('hover')) {
var addThis = parseInt(this.model.get('steals')) + 1;
this.model.save({steals: addThis});
}
}
// 1 for one point
if(e.keyCode == 49) {
if(this.$el.hasClass('hover')) {
var addMake = parseInt(this.model.get('made_one')) + 1;
this.model.save({made_one: addMake});
var addOne = parseInt(this.model.get('points')) + 1;
this.model.save({points: addOne});
}
}
// 2 for two points
if(e.keyCode == 50) {
if(this.$el.hasClass('hover')) {
var addMake = parseInt(this.model.get('made_two')) + 1;
this.model.save({made_two: addMake});
var addTwo = parseInt(this.model.get('points')) + 2;
this.model.save({points: addTwo});
}
}
// 2 for two points
if(e.keyCode == 51) {
if(this.$el.hasClass('hover')) {
var addMake = parseInt(this.model.get('made_three')) + 1;
this.model.save({made_three: addMake});
var addThree = parseInt(this.model.get('points')) + 3;
this.model.save({points: addThree});
}
}
}
This is cool for my app because when the user hovers over the item the user can hit a key to add data, instead of clicking.
So you are only going to be able to listen to the keypress in whichever element that you have the listener set on (or its children). And the keypress event is only going to fire if the element is focused. So I think the best solution for you would be to set focus on the element you are hovering over, then you can listen for the keypress, or better yet, listen to keydown because it behaves in a more standard way cross browser.
Here is a working JSFiddle demonstrating this technique: http://jsfiddle.net/DfjF2/2/
Only certain form elements accept focus. You can add contenteditable or tabindex attributes to the element, and that should allow pretty much any element to receive focus, but then the keydown event won't actually get fired! This is a browser specific issue. In my experience, a <span> will cause keydown and keyup events to be fired in every browser I have tested (Chrome, Firefox, IE, Safari, Android browser, Silk). So in the jsfiddle I added a span inside the target element, put focus on that, and added the keydown event listener to it.
So if you added an empty <span> into your view, your code could look something like this:
var PlayerView = Backbone.View.extend({
tagName: 'div',
events: {
'click .points, .assists, span.rebounds, span.steals':'addStat',
'mouseenter': 'enter',
'mouseleave': 'leave',
'keydown': 'keyAction'
},
enter: function() {
this.$el.addClass('hover');
var span = this.$el.find('span');
span.attr('tabindex', '1').attr('contenteditable', 'true');
span.focus();
},
leave: function() {
this.$el.removeClass('hover');
var span = this.$el.find('span');
span.removeAttr('contenteditable').removeAttr('tabindex');
span.blur();
},
keyAction: function(e) {
var code = e.keyCode || e.which;
if(code == 65) {
alert('add assist')
}
}
});

Jquery: Executing a function on keypress on google.com

I am trying to capture the search query from google.com when the "enter" key is pressed.
I am using the following code to test that the event is actually being triggered:
$(document).keypress(function(e) {
if(e.which == 13) {
alert('You pressed enter!');
}
});
This does not work when the focus is in the query box, but works fine otherwise I assume this is because an event is not being bubbled up by the auto-complete JS? Any ideas what might be happening, and how I can get the keypress event to fire when the focus is on the query box, which is the case when a query is being entered?
You can try $(window).on('keyup', function() {}); of you can bind the same handler to the search input.
Use "hashchange" event
It is triggered when triggered "auto-complete JS"
$(window).on("hashchange", function() {
var query = getKeywordsFromUrl(document.location.href);
} );
function getKeywordsFromUrl(href) {
var reStr = "google.com/search.*[&?]q=(.*?)(&.*)?$";
var rx = new RegExp(reStr, 'm');
if (rx.test(href)) {
var parts = rx.exec(href);
if (parts[1]) {
var terms = decodeURI(parts[1]).split("+");
return terms;
}
}
return null;
}

Invoke a function after right click paste in jQuery

I know we can use bind paste event as below:
$('#id').bind('paste', function(e) {
alert('pasting!')
});
But the problem is, that it will call before the pasted text paste. I want a function to be triggered after the right click -> paste text pasted on the input field, so that I can access the pasted value inside the event handler function.
.change() event also doesn't help. Currently I use .keyup() event, because I need to show the remaining characters count while typing in that input field.
Kind of a hack, but:
$("#id").bind('paste', function(e) {
var ctl = $(this);
setTimeout(function() {
//Do whatever you want to $(ctl) here....
}, 100);
});
Why not use the "input" event?
$("#id").bind('input', function(e) {
var $this = $(this);
console.log($this.val());
});
This will stop user from any pasting, coping or cutting with the keyboard:
$("#myField").keydown(function(event) {
var forbiddenKeys = new Array('c', 'x', 'v');
var keyCode = (event.keyCode) ? event.keyCode : event.which;
var isCtrl;
isCtrl = event.ctrlKey
if (isCtrl) {
for (i = 0; i < forbiddenKeys.length; i++) {
if (forbiddenKeys[i] == String.fromCharCode(keyCode).toLowerCase()) {
return false;
}
}
}
return true;
});
This one will do the same for the mouse events:
$("#myField").bind("cut copy paste",function(event) {
event.preventDefault();
});
Even though the above one will not prevent right clicks, the user will not be able to paste, cut or copy from that field.
To use it after the event, like you wondered on your question, you must use JavaScript Timing Event
setTimeout(function() {
// your code goes here
}, 10);
I had the same issue, I opted to replicate the paste action through javascript and use that output instead:
var getPostPasteText = function (element, pastedData) {
// get the highlighted text (if any) from the element
var selection = getSelection(element);
var selectionStart = selection.start;
var selectionEnd = selection.end;
// figure out what text is to the left and right of the highlighted text (if any)
var oldText = $(element).val();
var leftPiece = oldText.substr(0, selectionStart);
var rightPiece = oldText.substr(selectionEnd, oldText.length);
// compute what the new value of the element will be after the paste
// note behavior of paste is to REPLACE any highlighted text
return leftPiece + pastedData + rightPiece;
};
See IE's document.selection.createRange doesn't include leading or trailing blank lines for source of the getSelection function.
No need to bind :
$(document).on('keyup input', '#myID', function () {
//Do something
});

Chrome (maybe Safari?) fires "blur" twice on input fields when browser loses focus

Here is an interesting jsfiddle.
In Firefox:
Run the fiddle
Click in text input
Click somewhere else. Should say "1 blurs".
Click in the text input again.
ALT-TAB to another window. Fiddle should now say "2 blurs".
In Chrome, at step 5, it says "3 blurs". Two separate "blur" events are fired when the whole browser loses focus. This is of interest because it means that it's not safe to assume, in a "blur" handler, that the element actually had focus just before the event was dispatched; that is, that the loss of focus — the transition from "being in focus" to "not being in focus" — is the reason for the event. When two "blur" events are generated, that condition is not satisfied during the handling of the second event, as the element is already not in focus.
So is this just a bug? Is there a way to tell that a "blur" event is bogus?
The reason it is firing twice is because of window.onblur. The window blurring triggers a blur event on all elements in that window as part of the way javascript's capturing/bubbling process. All you need to do is test the event target for being the window.
var blurCount = 0;
var isTargetWindow = false;
$(window).blur(function(e){
console.log(e.target);
isTargetWindow = true;
});
$(window).focus(function(){
isTargetWindow = false;
});
$('input').blur(function(e) {
if(!isTargetWindow){
$('div').text(++blurCount + ' blurs');
}
console.log(e.target);
});
​
http://jsfiddle.net/pDYsM/4/
This is confirmed Chrome bug. See the Chromium Issue Tracker
The workaround is in the accepted answer.
Skip 2nd blur:
var secondBlur = false;
this.onblur = function(){
if(secondBlur)return;
secondBlur = true;
//do whatever
}
this.onfocus = function(){
secondBlur = false;
//do whatever
}
This probably isn't what you want to hear, but the only way to do it seems to be to manually track whether the element is focused or not. For example (fiddle here):
var blurCount = 0;
document.getElementsByTagName('input')[0].onblur = function(e) {
if (!e) e = window.event;
console.log('blur', e);
if (!(e.target || e.srcElement)['data-focused']) return;
(e.target || e.srcElement)['data-focused'] = false;
document.getElementsByTagName('div')[0].innerHTML = (++blurCount + ' blurs');
};
document.getElementsByTagName('input')[0].onfocus = function(e) {
if (!e) e = window.event;
console.log('focus', e);
(e.target || e.srcElement)['data-focused'] = true;
};
Interestingly, I couldn't get this to work in jQuery (fiddle here) ... I really don't use jQuery much, maybe I'm doing something wrong here?
var blurCount = 0;
$('input').blur(function(e) {
console.log('blur', e);
if (!(e.target || e.srcElement)['data-focused']) return;
(e.target || e.srcElement)['data-focused'] = false;
$('div').innerHTML = (++blurCount + ' blurs');
});
$('input').focus(function(e) {
console.log('focus', e);
(e.target || e.srcElement)['data-focused'] = true;
});
You could also try comparing the event's target with document.activeElement. This example will ignore the alt+tab blur events, and the blur events resulting from clicking on Chrome's... chrome. This could be useful depending on the situation. If the user alt+tabs back into Chrome, it's as if the box never lost focus (fiddle).
var blurCount = 0;
document.getElementsByTagName('input')[0].onblur = function(e) {
if (!e) e = window.event;
console.log('blur', e, document.activeElement, (e.target || e.srcElement));
if ((e.target || e.srcElement) == document.activeElement) return;
document.getElementsByTagName('div')[0].innerHTML = (++blurCount + ' blurs');
};​
​
I'm on Chrome Version 30.0.1599.101 m on Windows 7 and this issue appears to have been fixed.
I am experiencing the same and the above posts make sense as to why. In my case I just wanted to know if at least one blur event had occurred. As a result I found that just returning from my blur function solved my issue and prevented the subsequent event from firing.
function handleEditGroup(id) {
var groupLabelObject = $('#' + id);
var originalText = groupLabelObject.text();
groupLabelObject.attr('contenteditable', true)
.focus().blur(function () {
$(this).removeAttr('contenteditable');
$(this).text($(this).text().substr(0, 60));
if ($(this).text() != originalText) {
alert("Change Found");
return; //<--- Added this Return.
}
});
}
Looks like an oddity of angularjs gives a simpler solution when using ng-blur; the $event object is only defined if you pass it in:
ng-blur="onBlur($event)"
so (if you aren't using ng-blur on the window) you can check for:
$scope.onBlur = function( $event ) {
if (event != undefined) {
//this is the blur on the element
}
}

Categories

Resources