AngularJS/ionic: Navigating specific screen items via direction keys - angularjs

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);
}
}
});
}

Related

Trying to addEventListener to looped array

I'm pretty sure I've coded the event handler part wrong. When the array goes through and loops it does product a list on the page. I just need that list clickable with an event listener, then a series of re-direct based on if statements for comparison...ideas ?
window.onload = eventMonitor;
function eventMonitor(){
document.getElementById("names").addEventListener('click', reRoute, false);
document.getElementById("hiddenText").addEventListener('mouseover', treasureRoute, false);
function createName(){
var streetNames = ["Carmen", "Napoli", "Oscar", "Haven", "Tampa"];
//this top partscript creates a for loop that will take each array [item] and simply right it to the screen.
for (i=0; i<streetNames.length; i++){
var mName = "Martin";
var node = document.createElement("li");
var textNode = document.createTextNode(mName + " " + (streetNames[i]));
node.appendChild(textNode);
document.getElementById("names").appendChild(node);
}
}
function reRoute(){
if(names === 'Martin Carmen'){
routeWindow = window.open("https://en.wikipedia.org/wiki/MSC_Carmen");
}
else if(names === 'Martin Napoli'){
routeWindow = window.open("https://en.wikipedia.org/wiki/MSC_Napoli")
}
else if(names === 'Martin Oscar'){
routeWindow = window.open("https://en.wikipedia.org/wiki/MSC_Oscar")
}
else if(names === 'Martin Haven'){
routeWindow = window.open("https://en.wikipedia.org/wiki/MT_Haven")
}
else if(names === 'Martin Tampa'){
routeWindow = window.open("https://en.wikipedia.org/wiki/MV_Tampa")
}
}
function treasureRoute(){
shipWindow = window.open("file:///Users/User/something.html");
}
}
What you want to do does not require JavaScript. JavaScript, while useful in many circumstances, is error prone as you have noticed. The anchor tag is used to navigate between pages, which is why you inject <li>Text</li> elements into the DOM.
If you would nonetheless want to use JavaScript in a situation like yours, you would program your event handler to comply with the addEventListener API:
const names = document.getElementById('names');
names.addEventListnener('click', event => {
const { target } = event;
console.log(`${target.tagName} was clicked!`);
});

Alert and Confirmation dialog box AngularJs

I'm trying to implement Alert/Confirmation dialog boxes using the bootstrap modal service in my angular app. I want to make it generic, based on the parameter that I pass to the modal's controller it will either behave as the alert box where the user can just close it on reading the message on the modal and also it can behave like a confirmation dialog where the user should say either 'OK' or 'Cancel' I need to capture the response from the user which I'm able to. Now, the problem here is that i've list of items say in a grid and when ever user wants to delete an item from the list I need to show the confirmation message box and based on the user response I need to either delete the item or leave it if users wishes to leave it in the list, but my item is being deleted first and then the confirmation dialog is showing up, I've tried using the call back but still no use. Please help me if anyone came across this situation.
Method that shows the alert:
$scope.showAlertMessage = function (modalName,commands,callback)
{
var modalOptions = {};
var alertMessageText;
var okBtn;
var cancelBtn;
var autoCloseTimeout;
$scope.modalResp === false;
if (modalName === 'ItemNotEligible')
{
modalOptions['template'] = 'application/Views/ItemNotEligibleAlert.html';
modalOptions['cntrlr'] = 'itemAlertsController';
modalOptions['winClass'] = 'item-alert-win';
alertMessageText = commands.alertMessage.text;
okBtn=true;
cancelBtn = false;
autoCloseTimeout=commands.alertMessage.autoDismissalTimeout;
}
else if (modalName === 'ItemDelete')
{
modalOptions['template'] = 'application/Views/ItemNotEligibleAlert.html';
modalOptions['cntrlr'] = 'itemAlertsController';
modalOptions['winClass'] = 'item-alert-win';
alertMessageText = commands.alertMessage.text;
okBtn = true;
cancelBtn = true;
autoCloseTimeout = commands.alertMessage.autoDismissalTimeout;
}
var params = { alertMessage: alertMessageText};
var modalInstance=$modal.open({
templateUrl: modalOptions.template,
controller: modalOptions.cntrlr,
windowClass: modalOptions.winClass,
resolve: {
modalParams: function () {
return params;
}
}
});
modalInstance.result.then(function (selected) {
$scope.modalResp = selected; //Response from the dialog
});
callback($scope.modalResp);
}
Method where the delete item logic exists and call to show alert method is made
this.voidItem = function (skuid) {
alertMessage.text = 'Are you sure you want to remove <strong>' + itemdata[itmid].split('|')[0] + '</strong> from your list?';
$scope.showAlertMessage('ItemDelete', commands, function (userresp) {
if (userresp === true) {
var lineId = 0;
for (var i = itemListState.length - 1; i >= 0; i--) {
if (parseInt(itemListState[i].item) === itmid && Boolean(itemListState[i].isItemVoid) != true) {
lineId = itemListState[i].Id;
if (lineId != 0) {
break;
}
}
}
poService.DeleteItem(itmid, lineId, function (modal) {
virtualtable = modal;
$scope.itemCount = $scope.itemCount - 1;
$scope.createlist(itmid);
});
}
});
}
The problem is that you are executing the callback instead of chain the method to the result of the promise
modalInstance.result.then(function (selected) {
callback($scope.modalResp); //Once the promise is solved, execute your callback
$scope.modalResp = selected; //Why you need to save the response?
// If the user press cancel o close the dialog the promise will be rejected
}, onUserCancelDialogFn);
A more simple way:
modalInstance.result
.then(callback, onErrBack);

Disable drag-drop while HTML5 file browse popup is open

I am working on an Angular JS application which has HTML5 file input button and a Drag - Drop area.
I need to disable the drag drop area as long as the file browse popup is open. What is the best way to achieve this?
Here is an example I just made for you, this is an updated Google DnD Controller that I made more advanced plus added the functionality you wanted.
window.fileBrowserActive = false; //init the controller to false
function DnDFileController(selector, onDropCallback) {
var el_ = document.querySelector(selector);//select our element
var overCount = 0;//over count is 0
this.dragenter = function(e)
{
e.stopPropagation();//stop propagation and prevent the default action if any
e.preventDefault();
overCount++;//increment and assign the overCount var to 1
if(fileBrowserActive == false)//if the fileBrowserAction is false we can add dropping class
{
el_.classList.add('dropping');
}
else //else add error or none at all it's your choice
{
el_.classList.add('error');
}
};
this.dragover = function(e)
{
e.stopPropagation();
e.preventDefault();
};
this.dragleave = function(e)
{
e.stopPropagation();
e.preventDefault();
if (--overCount <= 0) { //we decrement and assign overCount once if it's equal to or less than 0 remove the classes and assign overCount to 0
el_.classList.remove('dropping');
el_.classList.remove('error');
overCount = 0;
}
};
this.drop = function(e) {
e.stopPropagation();
e.preventDefault();
el_.classList.remove('dropping');
el_.classList.remove('error');
if(fileBrowserActive === false)
{ //if fileBrowserActive is false send the datatranser data to the callback
onDropCallback(e.dataTransfer);
}
else
{ //else send false
onDropCallback(false);
}
};
el_.addEventListener('dragenter', this.dragenter, false);
el_.addEventListener('dragover', this.dragover, false);
el_.addEventListener('dragleave', this.dragleave, false);
el_.addEventListener('drop', this.drop, false);
}
var isFileBrowserActive = function(e){
fileBrowserActive = true; //once clicked on it is active
this.onchange = function(e){ //create the onchange listener
if(e.target.value !== "") //if the value is not blank we keep it active
{ //so now user can't DnD AND use the file browser
fileBrowserActive = true;
}
else if(e.target.value == "") //if it is blank means they click cancel most likely
{
fileBrowserActive = false;//assign false back
} //remove the listener
this.removeEventListener('change',this.onchange,false);
};
//assign the listener for change
this.addEventListener('change',this.onchange,false);
};
var fB = document.getElementById('fileBrowser'); //grab our element for file input
fB.addEventListener('click',isFileBrowserActive,false); //assign the click event
var activation = new DnDFileController('#dragNDrop',function(e){ //grab our DnD element
console.log(e); //console log the e (either datatransfer || false)
});
Check out the This Fiddle to see how this works

Angular NgStyle background image isn't refreshing

I am trying to change the background image based on window resize. I am getting the correct output in console in terms of the image path that I need. But for some reason the url just doesn't update in the div.
This is in a directive:
angular.element($window).on('resize', function(){
waitForFinalEvent(function(){
checkSize();
}, 500);
});
$scope.index = 0;
var checkSize = function(){
var width = angular.element($window).width();
var height = angular.element($window).height()
console.log('w: ' +width);
console.log('h: '+height);
$scope.index ++;
if(width < 1050 && width > 800 ) {
$scope.slideImage = $scope.displaySlideImageM;
console.log('here1: ' +$scope.slideImage);
} else if(width < 799 && height < 800) {
$scope.slideImage = $scope.displaySlideImageM;
console.log('here2: ' +$scope.slideImage);
} else if(width < 799 && height > 800) {
$scope.slideImage = $scope.displaySlideImageP;
console.log('here3: ' +$scope.slideImage);
} else {
$scope.slideImage = $scope.displaySlideImageO;
console.log('here4: ' +$scope.slideImage);
}
}
var waitForFinalEvent = (function () {
var timers = {};
return function (callback, ms) {
if (timers) {
clearTimeout(timers);
}
timers = setTimeout(callback, ms);
};
})();
html:
<div class="result-slide" ng-style="{'background-image':'url('+ slideImage +'?v='+ index +')'}"></div>
As you can see I tried adding a random param to the end of the url with ?v=n to attempt to trigger the refresh. But although the index changes, the physical slideImage url isn't updating.
Can someone please shed some light to this issue?
A quick scan of your code shows that you should be using $timeout, instead of setTimeout. While both things execute the timer fine, setTimeout occurs outside of angular so the $scope assignments will not be updated. If you do use setTimeout, then you need to wrap your assignment code in $scope.apply().
You can read about it more here. https://docs.angularjs.org/api/ng/service/$timeout

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

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.

Resources