I'm currently trying to implement shortcuts feature on the menu of my web based project. I had already implemented single or double shortcut key combination (like F1, CTRL + Q etc..,).
$("#MyField").keydown(function (eventData) {
if (eventData.keyCode == 112) {
eventData.preventDefault();
myFunction_ToCall();
}
});
But now I'm moving towards the combination of 3-keys, to access a sub-subMenu, because my menu is look like this:
Menu1
SubMenu1
Sub-SubMenu1
Sub-Sub-Menu2
SubMenu2
SubMenue3
Menu2
Menu3
Menu4
So, to access the 1. Sub-SubMenu1 the path will be like 1. Menu1 > 1. SubMenu1 > 1. Sub-SubMenu1, the key combination will be like CTRL + 1 + 1 + 1`.
I searched a lot, but couldn't find any better solution. And now I'm confused how to achieve it. Anyone can help me!!
I would use KeyboardEvent.key, KeyboardEvent.ctrlKey and a tree where each sequence of keystrokes forms a branch :
step = shortcuts = {
"1": {
"1": sayHello,
"2": sayGoodbye
}
};
document.addEventListener("keydown", function (ev) {
if (ev.key === "Control") {
step = shortcuts; // go back to the root
ev.preventDefault();
}
});
document.addEventListener("keyup", function (ev) {
if (ev.ctrlKey && step[ev.key]) {
step = step[ev.key]; // a node was reached
if (typeof step === "function") {
step(); // a leaf was reached
}
}
});
function sayHello () {
console.log("Hello :-)");
}
function sayGoodbye () {
console.log("Goodbye :-(");
}
<p>Click here before trying shortcuts.</p>
Here is an improved version of the previous snippet :
step = shortcuts = {
"1": {
"1": "sayHello",
"2": "sayGoodbye"
}
};
commands = {
"sayHello": function () {
console.log("Hello :-)");
},
"sayGoodbye": function () {
console.log("Goodbye :-(");
}
};
printShortcuts(shortcuts, "CTRL");
document.addEventListener("keydown", function (ev) {
if (ev.key === "Control") {
step = shortcuts; // go back to the root
ev.preventDefault();
}
});
document.addEventListener("keyup", function (ev) {
if (ev.ctrlKey && step[ev.key]) {
step = step[ev.key]; // a node was reached
if (commands[step]) {
commands[step](); // a leaf was reached
}
}
});
function printShortcuts (node, prefix) {
if (typeof node === "string") {
document.body.innerHTML += prefix + " : " + node + "()<br>";
} else {
for (var child in node) {
printShortcuts(node[child], prefix + "-" + child);
}
}
}
<p>Click here before trying shortcuts.</p>
How about this example from Keyboard events with Ctrl, Alt, Shift keys
Take a look at how it captures combinations..
function handleKeyDown(e) {
var ctrlPressed=0;
var altPressed=0;
var shiftPressed=0;
var evt = (e==null ? event:e);
shiftPressed=evt.shiftKey;
altPressed =evt.altKey;
ctrlPressed =evt.ctrlKey;
self.status=""
+ "shiftKey="+shiftPressed
+", altKey=" +altPressed
+", ctrlKey=" +ctrlPressed
if ((shiftPressed || altPressed || ctrlPressed)
&& (evt.keyCode<16 || evt.keyCode>18))
alert ("You pressed the "+fromKeyCode(evt.keyCode)
+" key (keyCode "+evt.keyCode+")\n"
+"together with the following keys:\n"
+ (shiftPressed ? "Shift ":"")
+ (altPressed ? "Alt " :"")
+ (ctrlPressed ? "Ctrl " :"")
)
return true;
}
document.onkeydown = handleKeyDown;
Related
I have a "show link" that when clicked displays a hidden < li > containing other links.
These links happen to display at the exact co-ordinates of the "show link".
When "show Link" is clicked, its event is fired, but then the link below also is triggered.
How to I stop the newly shown links from being clicked when I click "show link"?
Edit:
I am providing the code, but it may complicate the issue. The setTapClickAction is to avoid the double click behaviour that you get using .on("touchstart click")
Inline script:
...
let $m = $('<a href="#"/>').text('Show Link');
$.setTapClickAction($m, function (el, e) {
$('li.location').fadeIn( 1000);
$(el).text("Show All").attr("href","https://example.com")
});
$('<p id="more-locations"/>').html($m).insertAfter(list);
...
main.js:
// function to set the tap or click action on an element.
// suggested usage:
// $.setTapClickAction('.subscription_show_button', function(){
// $modalElement.modal('show');
// });
$.setTapClickAction = function (selector, actionFunction){
if (typeof actionFunction !== 'function' ){
console.log('No Action Function given. Function tapClickButton');
return false;
}
let $obj;
if (typeof selector === 'string'){
$obj = $(selector);
} else if (selector instanceof $) {
$obj = selector;
} else {
console.log('No element for action: ' + selector);
return false;
}
let touchmoved;
$obj.on('click',function(e){
actionFunction($(this), e);
console.log("click fired by " + this);
}).on('touchend',function (e) {
if (touchmoved !== true) {
actionFunction($(this), e);
}
}).on('touchstart', function () {
$(this).off('click');
touchmoved = false;
console.log("touchstart fired by " + this);
}).on('touchmove', function () {
touchmoved = true;
});
};
edit2:
Here is a link to the production site. https://t.starstarmobile.com/5/SESSIONIDB10/quick2?phone=8887186545 click or tap the "find other centers near you"
So my answer to the problem was to use .preventDefault() on any links that did not have an href value. I also added namespaces so that the events could be modified multiple times.
// function to set the tap or click action on an element.
// suggested usage:
// $.setTapClickAction('.subscription_show_button', function(){
// $subscriptionModal.modal('show');
// });
$.setTapClickAction = function (selector, actionFunction) {
let $obj, touchmoved, hasHref;
let namespace = "";
if (typeof actionFunction !== 'function') {
console.log('No Action Function given. Function tapClickButton');
return false;
}
if (typeof selector === 'string') {
$obj = $(selector);
//set the name space
namespace = selector.charAt(0) !== '.' ? '.' + selector : selector;
// console.log("string selector", selector);
} else if (selector instanceof $) {
$obj = selector;
// console.log("jquery Instance", selector);
} else {
console.log('No element for action:', selector);
return false;
}
//look for valid href or exec e.preventDefault
let href = $obj.attr("href");
if (href !== '#' && href !== undefined) {
hasHref = true;
// console.log ("Href: " + href)
}
//remove previously set events
$obj.off('click' + namespace);
$obj.off('touchstart' + namespace);
$obj.off('touchend' + namespace);
$obj.off('touchmove' + namespace);
//set events
// console.log('namespace: '+ namespace);
$obj.on('click' + namespace, function (e) {
if (!hasHref) {
e.preventDefault();
}
actionFunction($(this), e);
// console.log("click fired by ", this);
}).on('touchend' + namespace, function (e) {
if (touchmoved !== true) {
actionFunction($(this), e);
}
}).on('touchstart' + namespace, function (e) {
if (!hasHref) {
e.preventDefault();
}
$(this).off('click' + namespace);
touchmoved = false;
// console.log("touchstart fired by:", this, e.currentTarget.getAttribute("href"));
}).on('touchmove' + namespace, function () {
touchmoved = true;
});
};
I have been trying to trigger right click even when the users left click.
I have tried trigger, triggerHandler, mousedown but I wasn't able to get it to work.
I'm able to catch the click events themselves but not able to trigger the context menu.
Any ideas?
To trigger the mouse right click
function triggerRightClick(){
var evt = new MouseEvent("mousedown", {
view: window,
bubbles: true,
cancelable: true,
clientX: 20,
button: 2
});
some_div.dispatchEvent(evt);
}
To trigger the context menu
function triggerContextMenu(){
var evt = new MouseEvent("contextmenu", {
view: window
});
some_div.dispatchEvent(evt);
}
Here is the bin: http://jsbin.com/rimejisaxi
For better reference/explanation: https://stackoverflow.com/a/7914742/1957036
Use the following code for reversing mouse clicks.
$.extend($.ui.draggable.prototype, {
_mouseInit: function () {
var that = this;
if (!this.options.mouseButton) {
this.options.mouseButton = 1;
}
$.ui.mouse.prototype._mouseInit.apply(this, arguments);
if (this.options.mouseButton === 3) {
this.element.bind("contextmenu." + this.widgetName, function (event) {
if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
$.removeData(event.target, that.widgetName + ".preventClickEvent");
event.stopImmediatePropagation();
return false;
}
event.preventDefault();
return false;
});
}
this.started = false;
},
_mouseDown: function (event) {
// we may have missed mouseup (out of window)
(this._mouseStarted && this._mouseUp(event));
this._mouseDownEvent = event;
var that = this,
btnIsLeft = (event.which === this.options.mouseButton),
// event.target.nodeName works around a bug in IE 8 with
// disabled inputs (#7620)
elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
return true;
}
this.mouseDelayMet = !this.options.delay;
if (!this.mouseDelayMet) {
this._mouseDelayTimer = setTimeout(function () {
that.mouseDelayMet = true;
}, this.options.delay);
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted = (this._mouseStart(event) !== false);
if (!this._mouseStarted) {
event.preventDefault();
return true;
}
}
// Click event may never have fired (Gecko & Opera)
if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
$.removeData(event.target, this.widgetName + ".preventClickEvent");
}
// these delegates are required to keep context
this._mouseMoveDelegate = function (event) {
return that._mouseMove(event);
};
this._mouseUpDelegate = function (event) {
return that._mouseUp(event);
};
$(document)
.bind("mousemove." + this.widgetName, this._mouseMoveDelegate)
.bind("mouseup." + this.widgetName, this._mouseUpDelegate);
event.preventDefault();
mouseHandled = true;
return true;
}
});
Now at the function calling event use mouseButton : 3 for right click and 1 for left click
I'm new to JavaScript, coming over from Swift. Trying it out code-learning challenges at http://play.elevatorsaga.com/
and some behavior is tough to grasp. In the following code, I setup floor & elevator objects. I am trying to get the elevator to get the floor it is about to pass's button request (if someone has pressed that floor's up or down button to call the elevator)
- in the code console.log(" (x) passing_floor - Same direction requested");
However the logs I get tell me that the up/downRequests are undefined
passing_floor 2 up
upRequest: undefined
downRequest: undefined
Is the issue with initialization? scoping? What is the proper way to achieve what I am trying to do?
{
init: function(elevators, floors) {
function initializeElevator(elevator){
elevator.on("floor_button_pressed", function(floorNum) {
elevator.goToFloor(floorNum);
});
elevator.on("idle", function() {
elevator.goToFloor(0);
});
elevator.on("passing_floor", function(floorNum, direction) {
if ((floorNum.upRequest) && (direction =='up')) {
floorNum.upRequest = false;
console.log(" (x) passing_floor - Same direction requested");
} else if ((floorNum.downRequest) && (direction == 'down')) {
floorNum.downRequest = false;
console.log(" (x) passing_floor - Same direction requested");
} else {
console.log("passing_floor " + floorNum + " " + direction);
console.log("upRequest: " + floorNum.upRequest);
console.log("downRequest: " + floorNum.downRequest);
}
});
}
function initializeFloor(floor){
var upRequest = false;
var downRequest = false;
floor.on("up_button_pressed", function() {
this.upRequest = true;
});
floor.on("down_button_pressed", function() {
this.downRequest = true;
});
}
elevators.forEach(initializeElevator);
floors.forEach(initializeFloor);
},
update: function(dt, elevators, floors) { }
}
Thank you for taking the time to help me understand Javascript a bit more, trying out W3School to get around it, but let me know if you have better sites I should look at..
I was able to proceed with the desired behavior by creating an "array" of floors that were global - I'm not sure if this was a scoping issue or something else though!
{
init: function(elevators, floors) {
var floorArray = new Array(floors.length);
var elevatorOnFloor = new Array(floors.length);
//the init function populates the listeners for the elevator objects
function initializeElevator(elevator){
elevator.on("floor_button_pressed", function(floorNum) {
elevator.goToFloor(floorNum, true);
});
elevator.on("idle", function() {
elevator.goToFloor(0);
});
elevator.on("passing_floor", function(floorNum, direction) {
if ((floorArray[floorNum] == 1) && (direction =='up')) {
floorArray[floorNum] = 0;
elevator.goToFloor(floorNum, true);
console.log("picking someone else going UP");
elevatorOnFloor[floorNum]+=1;
console.log("elevators per floor:"+ elevatorOnFloor );
} else if ((floorArray[floorNum] = 2) && (direction == 'down')) {
floorArray[floorNum] = 0;
elevator.goToFloor(floorNum, true);
console.log("picking someone else going DOWN");
elevatorOnFloor[floorNum]-=1;
console.log("elevators per floor:" + elevatorOnFloor + floorArray );
} else {
console.log("passing_floor " + floorNum + " " + direction);
if (direction =='up') {
elevatorOnFloor[floorNum]+=1;
} else {
elevatorOnFloor[floorNum]-=1;
}
}
});
}
function initializeFloor(floor){
floor.on("up_button_pressed", function() {
floorArray[floor] = 1;
});
floor.on("down_button_pressed", function() {
floorArray[floor] = 2;
});
}
function initializeElevatorsOnFloor(floor){
elevatorOnFloor[floor] = 0;
}
// we initialize all the elevators
elevators.forEach(initializeElevator);
// initialize the floors
floors.forEach(initializeFloor);
floors.forEach(initializeElevatorsOnFloor);
},
update: function(dt, elevators, floors) {
// We normally don't need to do anything here
}
}
I have a problem to detect shift + right arrow + p in angular.
I am using angular 1.2.3
I have no problem to detect only right arrow + p but when the shift comes into game something brakes
The question is hot to detect situation when 3 keys are pressed: SHIFT + RIGHT ARROW + P
Here is a working example on plunker
var app = angular.module('keyboardDemo', []);
app.controller('MainCtrl', function($scope, $timeout) {
/**
* 39 (right arrow)
* 80 (p)
*/
var map = {39: false, 80: false};
$scope.onKeyUp = function(event){
if (map[39] && map[80]) {
$scope.data.message1 = "P + RIGHT pressed!";
$timeout(function(){
$scope.data.message1 = '';
}, 1000);
}
if (event.shiftKey && map[39] && map[80]) {
$scope.data.message2 = "SHIFT + P + RIGHT pressed!";
$timeout(function(){
$scope.data.message2 = '';
}, 1000);
}
var keyCode = getKeyboardEventCode(event);
if (keyCode in map) {
clearKeyCode(keyCode);
}
};
$scope.onKeyDown = function(event){
var keyCode = getKeyboardEventCode(event);
if (keyCode in map) {
map[keyCode] = true;
}
}
var getKeyboardEventCode = function (event) {
return parseInt((window.event ? event.keyCode : event.which));
};
function clearKeyCode(code){
map[code] = false;
}
$scope.data = {
'message1': '',
'message2': ''
};
});
As said in the comment. Your code works actually well . Using chrome on macOS, when i press "SHIFT + P + RIGHT ARROW" and release the keys i see both messages.
I am trying to use the history api to make some rudimentary filtering a bit more usable for people using my site.
I have it working quite well for the most part but I am stuck on some edge cases: hitting the start of the history chain (and avoiding infinte back) and loosing the forward button.
The full source with working examples can be found here: http://jsfiddle.net/YDFCS/
The JS code:
$(document).ready(function () {
"use strict";
var $noResults, $searchBox, $entries, searchTimeout, firstRun, loc, hist, win;
$noResults = $('#noresults');
$searchBox = $('#searchinput');
$entries = $('#workshopBlurbEntries');
searchTimeout = null;
firstRun = true;
loc = location;
hist = history;
win = window;
function reset() {
if (hist.state !== undefined) { // Avoid infinite loops
hist.pushState({"tag": undefined}, "theMetaCity - Workshop", "/workshop/");
}
$noResults.hide();
$entries.fadeOut(150, function () {
$('header ul li', this).removeClass('searchMatchTag');
$('header h1 a span', this).removeClass('searchMatchTitle'); // The span remains but it is destroyed when filtering using the text() function
$(".workshopentry", this).show();
});
$entries.fadeIn(150);
}
function filter(searchTerm) {
if (searchTerm === undefined) { // Only history api should push undefined to this, explicitly taken care of otherwise
reset();
} else {
var rePattern = searchTerm.replace(/[.?*+^$\[\]\\(){}|]/g, "\\$&"), searchPattern = new RegExp('(' + rePattern + ')', 'ig'); // The brackets add a capture group
$entries.fadeOut(150, function () {
$noResults.hide();
$('header', this).each(function () {
$(this).parent().hide();
// Clear results of previous search
$('li', this).removeClass('searchMatchTag');
// Check the title
$('h1', this).each(function () {
var textToCheck = $('a', this).text();
if (textToCheck.match(searchPattern)) {
textToCheck = textToCheck.replace(searchPattern, '<span class="searchMatchTitle">$1</span>'); //capture group ($1) used so that the replacement matches the case and you don't get weird capitolisations
$('a', this).html(textToCheck);
$(this).closest('.workshopentry').show();
} else {
$('a', this).html(textToCheck);
}
});
// Check the tags
$('li', this).each(function () {
if ($(this).text().match(searchPattern)) {
$(this).addClass('searchMatchTag');
$(this).closest('.workshopentry').show();
}
});
});
if ($('.workshopentry[style*="block"]').length === 0) {
$noResults.show();
}
$entries.fadeIn(150);
});
}
}
$('header ul li a', $entries).on('click', function () {
hist.pushState({"tag": $(this).text()}, "theMetaCity - Workshop - " + $(this).text(), "/workshop/tag/" + $(this).text());
$searchBox.val('');
filter($(this).text());
return false; // Using the history API so no page reloads/changes
});
$searchBox.on('keyup', function () {
clearTimeout(searchTimeout);
if ($(this).val().length) {
searchTimeout = setTimeout(function () {
var searchVal = $searchBox.val();
hist.pushState({"tag": searchVal}, "theMetaCity - Workshop - " + searchVal, "/workshop/tag/" + searchVal);
filter(searchVal);
}, 500);
}
if ($(this).val().length === 0) {
searchTimeout = setTimeout(function () {
reset();
}, 500);
}
});
$('#reset').on('click', function () {
$searchBox.val('');
reset();
});
win.addEventListener("popstate", function (event) {
console.info(hist.state);
if (event.state === null) { // Start of history chain on this page, direct entry to page handled by firstRun)
reset();
} else {
$searchBox.val(event.state.tag);
filter(event.state.tag);
}
});
$noResults.hide();
if (firstRun) { // 0 1 2 3 4 (if / present)
var locArray = loc.pathname.split('/'); // '/workshop/tag/searchString/
if (locArray[2] === 'tag' && locArray[3] !== undefined) { // Check for direct link to tag (i.e. if something in [3] search for it)
hist.pushState({"tag": locArray[3]}, "theMetaCity - Workshop - " + locArray[3], "/workshop/tag/" + locArray[3]);
filter(locArray[3]);
} else if (locArray[2] === '') { // Root page and really shouldn't do anything
hist.pushState({"tag": undefined}, "theMetaCity - Workshop", "/workshop/");
} // locArray[2] === somepagenum is an actual page and what should be allowed to happen by itself
firstRun = false;
// Save state on first page load
}
});
I feel that there is something I am not quite getting with the history api. Any help would be appreciated.
You need to use onpopstate event handler for the back and forward capabilities:
https://developer.mozilla.org/en-US/docs/Web/API/window.onpopstate
Check out this question I answered a while back, I believe they had the same issues you are facing:
history.pushstate fails browser back and forward button