How to watch for a keypress combination in Angularjs? [duplicate] - angularjs

This question already has answers here:
AngularJS - Multiple keypress at the same time
(4 answers)
Closed 4 years ago.
I'm trying to get my controller to watch for a combination of keys. For argument's sake, let's say: up up down down left right left right b a. How can I get angular to look out for these regardless of where in the page the user currently is?

Looks like you can use the ng-keydown to do this.
Here is a working plunker.
For this sample, I just bound ng-keydown to <body>. Works pretty well to catch all the keyboard events globally.
As #charlietfl points out, ng-keydown registers a lot of keyboard events so to make this usable would be a lot of work. For example, if you were trying to listen for a combination (like ctrl + r), then the ctrl key will register many times.
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.keyBuffer = [];
function arrays_equal(a,b) { return !(a<b || b<a); }
$scope.down = function(e) {
$scope.keyBuffer.push(e.keyCode);
var upUp = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
if (arrays_equal(upUp, $scope.keyBuffer)) {
alert('thats it!');
}
};
});
HTML:
<body ng-controller="Ctrl" ng-keydown="down($event)">

I'm using a different way to do it.
$scope.keyboard = {
buffer: [],
detectCombination: function() {
var codes = {};
this.buffer.forEach(function(code) {
codes['key_' + code] = 1;
});
if ((codes.key_91 || codes.key_93) && codes.key_8) {
// I'm looking for 'command + delete'
}
},
keydown: function($event) {
this.buffer.push($event.keyCode);
this.detectCombination();
},
keyup: function($event, week) {
this.buffer = [];
}
};

Detecting Backspace-Key (Mac) and Del-Key (PC):
<body ng-controller="Ctrl" ng-keydown="keyDown($event)">..<body>
$scope.keyDown = function(value){
if(value.keyCode == 46 || value.keyCode == 8) {
//alert('Delete Key Pressed');
}
};

This is all untested, but you could use ng-keypress
<body ng-keypress="logKeys($rootScope,$event)">...</body>
To call a function something like:
appCtrl.$scope.logKeys = function($rootScope,$event){
$rootScope.keyLog.shift(); // Remove First Item of Array
$rootScope.keyLog.push($event.keyCode); // Adds new key press to end of Array
if($scope.$rootScope.keyLog[0] !== 38) { return false; } // 38 == up key
if($scope.$rootScope.keyLog[1] !== 38) { return false; }
if($scope.$rootScope.keyLog[2] !== 40) { return false; } // 40 = down key
if($scope.$rootScope.keyLog[3] !== 40) { return false; }
if($scope.$rootScope.keyLog[4] !== 27) { return false; } // 37 = left key
if($scope.$rootScope.keyLog[5] !== 39) { return false; } // 39 = right key
if($scope.$rootScope.keyLog[6] !== 37) { return false; }
if($scope.$rootScope.keyLog[7] !== 39) { return false; }
if($scope.$rootScope.keyLog[8] !== 65) { return false; } // 65 = a
if($scope.$rootScope.keyLog[9] !== 66) { return false; } // 66 = b
$rootScope.doThisWhenAllKeysPressed(); // Got this far, must all match!
return true;
}
Outside an input field, I don't think ng-keypress works, but the keypress from angular-ui might.
I'm sure there should be an array diff kinda function too, but the specific call evades me right now.

Here's my take on it:
var app = angular.module('contra', []);
app.directive('code', function () {
function codeState() {
this.currentState = 0;
this.keys = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
this.keyPressed = function (key) {
if (this.keys[this.currentState] == key) {
this.currentState++;
if (this.currentState == this.keys.length) {
this.currentState = 0;
return true;
}
} else {
this.currentState = 0;
}
return false;
};
};
return {
restrict: 'A',
link: function (scope, element, attrs) {
var cs = new codeState();
scope.isValid = "NO";
element.bind("keydown", function (event) {
scope.$apply(function () {
if (cs.keyPressed(event.which)) {
scope.isValid = "YES";
console.log("CODE ENTERED");
} else {
scope.isValid = "NO";
}
});
});
}
}
});
What's different about this is it's a directive so if you attach this on the body, it'll apply to the whole page. This also allows for entering the code multiple times.
Plunkr:
http://plnkr.co/edit/tISvsjYKYDrSvA8pu2St

Check out this plunker. I've implemented a simple '2 UP keystrokes in a row' scenario.
You can do it in plain jQuery and communicate the event with a $rootScope.$broadcast.
Register the jQuery code in and Angular run callback (guarantees that angular has already bootstraped):
app.run(function($rootScope) {
var upHitOnce = false;
$(document).keyup(function(event) {
if (event.which == 38) {
if (upHitOnce) {
$rootScope.$broadcast('DoubleUpFired');
$rootScope.$apply();
upHitOnce = false;
} else {
upHitOnce = true;
}
} else {
upHitOnce = false;
}
});
});
and then any controller can listen to this event like:
$scope.$on('DoubleUpFired', function() {
$scope.fired = true;
});
Binding an ng-keydown action callback to body is ok, but has a small disadvantage. It fires a $digest on every keystroke. What you really want is a $digest only when the sequence has been entered when you somehow need to update the UI.
EDIT
See comments on how to remove actual jQuery dependency.

If you are trying 'ctrl+s' or 'commond+s' ( change the commondKey ) to do save, maybe can use like it :
directive :
(function () {
'use strict';
var lastKey = 0;
//var commondKey = 17;
var commondKey = 91;
angular
.module('xinshu')
.directive('saveEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown", function (event) {
if (event.which != commondKey && event.which != 83) {
lastKey = 0;
}
if (lastKey == commondKey && event.which == 83) {
scope.$apply(function () {
scope.$eval(attrs.saveEnter);
});
event.preventDefault();
}
lastKey = event.which;
});
};
});
})();
element :
<input id="title" save-enter="vm.saveTitle()"/>
You can rename the saveEnter in directive, with change the save-enter in html.
The 'vm.saveTitle()' is the fuc your want to do.

Related

Remove $watch and bring the logic out of the $watch in AngularJS

I have a code in AngularJS which looks like below :
$scope.startWatching = function () {
return $scope.$watch('form', function (n, o) {
var timeoutPromise;
$timeout.cancel(timeoutPromise); //does nothing, if timeout alrdy done
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
So whenever form changes, either $scope.showCompleteButton2() is called or $scope.showCompleteButton() is called.
The problem is that the $watch() gets called many number if times, so I need to bring these two methods out of the $watch().
Watchers like event listeners should only be added once when the DOM is built. And removed when the DOM is torn down.
If the code needs to enable or disable the actions performed by the watcher, provide a state in the model to do so:
var enableWatch = false;
$scope.startWatching = function () {
enableWatch = true;
};
var timeoutPromise;
$scope.$watch('form', function (n, o) {
if (!enableWatch) return;
//ELSE
timeoutPromise && $timeout.cancel(timeoutPromise);
timeoutPromise = $timeout(function () {
if (n !== o) {
if ($scope.isLegacy) {
$scope.showCompleteBtn = $scope.showCompleteButton2();
} else {
$scope.showCompleteBtn = $scope.showCompleteButton();
}
}
}, 400);
}, true);
The watcher ignores changes when the enableWatch variable is false. Set the variable to true to enable the specified actions.

Why my function called multiple times after scanning the barcode through barcode scanner device in angularjs?

I am working on point of sale application here i am facing an issue when i am scanning barcode through barcode scanner device, my function called multiple times. For example when i am scanning barcode first time my function call one time, when i am scanning barcode without refreshing the page then function called two times,when i am scanning barcode third times then function called 4 times,that means function called multiple of 2. Here is my code which i have done, please check and rectify my issue.
// This is my barcode scanner function, in this function there are two api called 1 for getting whole invoice through barcode which length is more than or equal to 15 and 2 for after scanning barcode to get product.
$scope.returnProductByScanner = function (cod) {
if($location.path() == "/returnSale"){
if(cod != undefined){
var n = cod.length;
if(n == 15 || n > 15){
var action = {"barcode": cod};
var getDt = customerService.viewInvoiceOnBarcode(action);
getDt.then(function(data){
if(data.status == "success"){
var so = data.SO;
so.return = "return";
var sop = data.SOProducts;
$scope.addParkedProductIntoBag(sop,so);
}else{
var msg = data.error;
$scope.responseMsg(msg,"Failed");
}
})
}else{
// if($scope.newBagListOfProduct.length > 0){
var action = {"barcode":cod,"userid":uid,"org_id":org_id};
var getDt = customerService.getBarcodeScannedData(action);
getDt.then(function(data){
if(data.status == "success"){
var prodData = data.product;
$scope.addProductInBagSaleReturn(prodData);
}else{
var msg = data.msg;
$scope.responseMsg(msg,"Failed");
}
})
// }else{
// var msg = "Please first add invoice for return!";
// $scope.responseMsg(msg,"Failed");
// }
}
}
//$('input[name="myInput"]').focus();
}else{
cod = undefined;
}
$('input[name="myInput"]').focus();
};
// This is my HTML code
<div>
<div data-barcode-scanner="returnProductByScanner"></div>
<div><input name="myInput" type="text"
data-ng-model="testvalueret"
id="t" autofocus/>
</div>
</div>
// This is the derective what i have used.
.directive('barcodeScanner', function() {
return {
restrict: 'A',
scope: {
callback: '=barcodeScanner',
},
link: function postLink(scope, iElement, iAttrs){
// Settings
var zeroCode = 48;
var nineCode = 57;
var enterCode = 13;
var minLength = 3;
var delay = 300; // ms
// Variables
var pressed = false;
var chars = [];
var enterPressedLast = false;
// Timing
var startTime = undefined;
var endTime = undefined;
jQuery(document).keypress(function(e) {
if (chars.length === 0) {
startTime = new Date().getTime();
} else {
endTime = new Date().getTime();
}
// Register characters and enter key
if (e.which >= zeroCode && e.which <= nineCode) {
chars.push(String.fromCharCode(e.which));
}
enterPressedLast = (e.which === enterCode);
if (pressed == false) {
setTimeout(function(){
if (chars.length >= minLength && enterPressedLast) {
var barcode = chars.join('');
//console.log('barcode : ' + barcode + ', scan time (ms): ' + (endTime - startTime));
if (angular.isFunction(scope.callback)) {
scope.$apply(function() {
scope.callback(barcode);
alert(barcode);
});
}
}
chars = [];
pressed = false;
},delay);
}
pressed = true;
});
}
};
})
Directives that add event handlers to external elements need to remove those event handlers when the scope is destroyed:
app.directive('barcodeScanner', function() {
return {
restrict: 'A',
scope: {
callback: '=barcodeScanner',
},
link: function postLink(scope, iElement, iAttrs){
jQuery(document).on("keypress", keypressHandler);
scope.$on("$destroy", function () {
jQuery(document).off("keypress", keypressHandler);
});
function keypressHandler(e) {
if (chars.length === 0) {
startTime = new Date().getTime();
} else {
endTime = new Date().getTime();
}
//...
}
}
}
})
The AngularJS framework builds and destroys elements in the course of its operation. When those elements are destroyed, their respective scope is also destroyed and any necessary cleanup should be performed.

AngularJS/ionic: Navigating specific screen items via direction keys

I am trying to implement a mechanism where specific items on a screen are navigable using arrows keys.
At the moment, I am drawing a red box around items as they move and pressing enter activates them.
I have the following directive:
(credits here and here)
.directive("moveNext", function() {
return {
restrict: "A",
link: function($scope, element,attrs) {
element.bind("keyup", function(e) {
if (e.which == 37) {
console.log ("MOVE LEFT:" + JSON.stringify(element));
element[0].classList.remove('selected');
var partsId = attrs.id.match(/move-(\d+)/);
console.log ("CURRENT PARTS="+JSON.stringify(partsId));
var currentId = parseInt(partsId[1]);
console.log ("Looking for move-"+(currentId-1));
var nextElement = angular.element(document.querySelectorAll('#move-' + (currentId - 1)));
// var $nextElement = element.next().find('movehere');
if(nextElement.length) {
nextElement[0].classList.add('selected');
nextElement[0].focus();
// $nextElement[0].style.border='5px solid red';;
}
}
if (e.which == 39) {
console.log ("MOVE RIGHT:" + JSON.stringify(element));
element[0].classList.remove('selected');
var partsId = attrs.id.match(/move-(\d+)/);
var currentId = parseInt(partsId[1]);
console.log ("CURRENT PARTS="+JSON.stringify(partsId));
var currentId = parseInt(partsId[1]);
var nextElement = angular.element(document.querySelectorAll('#move-' + (currentId + 1)));
console.log ("Looking for move-"+(currentId+1));
// var $nextElement = element.next().find('movehere');
if(nextElement.length) {
nextElement[0].classList.add('selected');
nextElement[0].focus();
// $nextElement[0].style.border='5px solid red';;
}
}
if (e.which == 13) {
console.log ("ENTER:" + JSON.stringify(element));
// element.triggerHandler('click');
}
});
if (event) event.preventDefault();
}
}
})
And then in the template I have the following, for example:
<div>
<button move-next id="move-1" ng-click="d1()">Yes</button>
<button move-next id="move-3" ng-click="d1()">Yes</button>
<button ng-click="d1()">No</button>
<button move-next id="move-2" ng-click="d1()">Yes</button>
</div>
Yes <!-- PROBLEM -->
Yes <!-- NEVER COMES HERE -->
The nice part is I can now navigate to any "clickable" element depending on the ID order I set, which is my intention. The problem is that focus() only works on items that are focusable, so once "move-4" is highlighted by the directive, the focus() doesn't really work so I can never move "next" to "move-5"
thanks
Problem solved:
I removed the directive, and instead wrote a global keyUpHandler
Inside the keyup handler, I kept state on last selected item ID, so I could +- it irrespective of whether an item is focusable or not.
I can now navigate arbitrary items on any view with direction pad.
The problem however is that move-Ids must be unique across views or I need to find a way to do a query only on the active view. I need to figure out how to do that. currentView = document.querySelector('ion-view[nav-view="active"]'); doesn't work.
The code (needs cleanup, but seems to work)
window.addEventListener('keyup', keyUpHandler, true);
function keyUpHandler(evt){
$timeout (function() {
var currentView = document.querySelector('ion-view[nav-view="active"]');
var keyCode=evt.keyCode;
var el, nextel;
if (keyCode == 13 ) {
if ($rootScope.dpadId >0) {
el = angular.element(currentView.querySelector('#move-' +$rootScope.dpadId));
el.triggerHandler('click');
}
return;
}
if (keyCode == 37 || keyCode == 39) {
if ($rootScope.dpadId < 1) {
console.log ("First dpad usage");
$rootScope.dpadId = 1;
el = angular.element(currentView.querySelector('#move-1'));
if (el.length) {
el[0].classList.add('selected');
}
} else {
// unselect old
el = angular.element(currentView.querySelector('#move-' +$rootScope.dpadId));
var nextId = (keyCode == 37) ? $rootScope.dpadId -1: $rootScope.dpadId + 1;
nextel = angular.element(currentView.querySelector('#move-' +nextId));
if (nextel.length) {
el[0].classList.remove('selected');
nextel[0].classList.add('selected');
$rootScope.dpadId = nextId;
}
console.log ("dpadID="+$rootScope.dpadId);
}
}
});
}

checkbox filter for json array in Angularjs

I have create a filter but this filter is not working with array inside array.
'http://plnkr.co/edit/oygy79j3xyoGJmiPHm4g?p=info'
Above plkr link is working demo.
app.filter('checkboxFilter', function($parse) {
var cache = { //create an cache in the closure
result: [],
checkboxData: {}
};
function prepareGroups(checkboxData) {
var groupedSelections = {};
Object.keys(checkboxData).forEach(function(prop) {
//console.log(prop);
if (!checkboxData[prop]) {
return;
} //no need to create a function
var ar = prop.split('=');
//console.log("ar is - "+ar);
if (ar[1] === 'true') {
ar[1] = true;
} //catch booleans
if (ar[1] === 'false') {
ar[1] = false;
} //catch booleans
/* replacing 0 with true for show all offers */
if(ar[0]=='SplOfferAvailable.text'){
ar[1]='true';
}else{
}
//make sure the selection is there!
groupedSelections[ar[0]] = groupedSelections[ar[0]] || [];
//at the value to the group.
groupedSelections[ar[0]].push(ar[1]);
});
return groupedSelections;
}
function prepareChecks(checkboxData) {
var groupedSelections = prepareGroups(checkboxData);
var checks = [];
//console.log(groupedSelections);
Object.keys(groupedSelections).forEach(function(group) {
//console.log("groupedSelections- "+groupedSelections);
//console.log("group- "+group);
var needToInclude = function(item) {
//console.log("item- "+item);
// use the angular parser to get the data for the comparson out.
var itemValue = $parse(group)(item);
var valueArr = groupedSelections[group];
//console.log("valueArr- "+valueArr);
function checkValue(value) { //helper function
return value == itemValue;
}
//check if one of the values is included.
return valueArr.some(checkValue);
};
checks.push(needToInclude); //store the function for later use
});
return checks;
}
return function(input, checkboxData, purgeCache) {
if (!purgeCache) { //can I return a previous 'run'?
// is the request the same as before, and is there an result already?
if (angular.equals(checkboxData, cache.checkboxData) && cache.result.length) {
return cache.result; //Done!
}
}
cache.checkboxData = angular.copy(checkboxData);
var result = []; // this holds the results
//prepare the checking functions just once.
var checks = prepareChecks(checkboxData);
input.every(function(item) {
if (checks.every(function(check) {
return check(item);
})) {
result.push(item);
}
return result.length < 10000000; //max out at 100 results!
});
cache.result = result; //store in chache
return result;
};
});
above code is for check box filter.
when i click on checkbox called "Availability" it does not filter the result.
Please help me out.
Thanks.
I think that the way you are navigating through json is wrong because if you put in this way it works
"Location": "Riyadh",
"AvlStatus": "AVAILABLE"
"Rooms": {.....
You have to go in some way through Rooms and right now I think you're not doing that

AngularJs reflect changes in current browser tab to other tab

Using this code, does angularjs supports binding that will also reflects the changes in the current tab you're working with into the other tab
<input type="text" ng-model="name"><span ng-bind="name"></span>
No, not using just that code.
However, I just wrote a nice directive for you to use with ng-model and ng-bind (does not work with just {{ inline expressions }}, though).
Here it is in action
And here is the code:
/**
* sync-between-tabs directive.
* Use in conjunction with ng-model or ng-bind to synchronise the contents
* between tabs. The value is synced using localStorage, so each thing to sync
* needs a unique key. Specify the key using the sync-between-tabs attribute
* value, or leave blank to use the ng-model or ng-bind attributes.
* Example usage:
* <input ng-model="some.thing" sync-between-tabs></input>
* uses the key "some.thing"
* <input ng-model="some.thing" sync-between-tabs="UNIQUEKEY25"></input>
* uses the key "UNIQUEKEY25"
* <span ng-bind="some.other.thing" sync-between-tabs></span>
* uses the key "some.other.thing"
* <span ng-bind="name" sync-between-tabs="UNIQUE_KEY_12"></span>
* uses the key "UNIQUE_KEY_12"
*/
app.directive('syncBetweenTabs', ['$window', '$parse',
function($window, $parse) {
var callbacks = {}, keysToWatch = [];
var localStorage = {
key: function(key) {
return '__syncValue_' + (key || '').replace('__syncValue_', '');
},
getItem: function(key) {
return $window.localStorage.getItem(localStorage.key(key));
},
setItem: function(key, val) {
$window.localStorage.setItem(localStorage.key(key), val);
},
onItemChange: function(key, callback) {
key = localStorage.key(key);
var keyAlreadyExists = false;
if (keysToWatch.indexOf(key) < 0) {
keysToWatch.push(key);
callbacks[key] = [callback];
} else {
callbacks[key].push(callback);
keyAlreadyExists = true;
}
return function deregister() {
if (!keyAlreadyExists)
keysToWatch = without(keysToWatch, key);
callbacks[key] = without(callbacks[key], callback);
if (callbacks.length === 0)
delete callbacks[key];
};
}
};
function without(arr, value) {
var newArr = [];
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] !== value)
newArr.push(arr[i]);
}
return newArr;
}
if ($window.addEventListener) {
$window.addEventListener("storage", handle_storage, false);
} else {
$window.attachEvent("onstorage", handle_storage);
}
function handle_storage(e) {
if (!e) e = $window.event;
if (callbacks[e.key])
angular.forEach(callbacks[e.key], function(callback) {
callback(e.newValue);
});
}
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
var key = attrs['syncBetweenTabs'],
mode = 'unknown',
dereg, ngBindExpr
if (ngModelCtrl) {
mode = 'ngModel';
if (!key)
key = attrs['ngModel'];
} else if (attrs['ngBind']) {
mode = 'ngBind';
if (!key)
key = attrs['ngBind'];
} else {
throw new Error('sync-between-tabs only works for ng-model and ng-bind at present');
}
if (mode == 'ngModel') {
ngModelCtrl.$viewChangeListeners.push(function() {
localStorage.setItem(key, ngModelCtrl.$viewValue);
});
var currentValue = localStorage.getItem(key);
if (currentValue && currentValue !== ngModelCtrl.$viewValue) {
ngModelCtrl.$setViewValue(currentValue);
ngModelCtrl.$render();
}
dereg = localStorage.onItemChange(key, function(value) {
ngModelCtrl.$setViewValue(value);
ngModelCtrl.$render();
});
} else {
ngBindExpr = $parse(attrs['ngBind']);
dereg = localStorage.onItemChange(key, function(value) {
ngBindExpr.assign(scope, value);
scope.$digest();
});
}
scope.$on('$destroy', dereg);
}
}
}
]);
assuming both tabs have the same site loaded under the same domain, you can use local/session storage or cookies to post the shared data and make it available to any tab, the downside you have to constantly check for changes. but it should work

Resources