Using the enter key as tab using only angularjs and jqlite - angularjs

I have looked at multiple threads and tried a vast variety of solutions.
Quite frankly I think I am losing my mind.
I have an ng-repeat with inputs. All that needs to happen is that when the user presses enter, it should shift focus to the next input, basically simulating the tab key functionality.
The code (incomplete):
HTML:
<body ng-app="ap" ng-controller="con">
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr ng-repeat='person in persons'>
<td>
<input type='text'
name="personName"
ng-model="person.name"
/>
</td>
<td>
<input type='number'
name="personName"
ng-model="person.age"
enter-as-tab
/>
</td>
</tr>
</table>
JS:
var app = angular.module("ap", []);
app.controller("con", function ($scope) {
$scope.persons = [
{ name: 'Susan', age: 1 },
{ name: 'Peter', age: 1 },
{ name: 'Jack', age: 2 }
];
});
app.directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
event.preventDefault();
// Go to next age input
}
});
};
});
Here is a link to the fiddle: fiddle

Ok, so I figured it out. Wasn't that difficult after all. Just got caught up in the whole "don't think jQuery while using Angular" mindset.
Here is the directive that I implemented:
app.directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
event.preventDefault();
var elementToFocus = element.next('tr').find('input')[1];
if(angular.isDefined(elementToFocus))
elementToFocus.focus();
}
});
};
});
Here is the link to the working fiddle: enter-as-tab

Starting from #avn's solution I made some changes to find recursively and focus to the next input text or input number, but only if the value is valid, or send the form. Was designed for ionic forms but could be adapted for any angular forms:
app.directive('enterAsTab', function () {
return {
restrict: 'A',
require: '^ngModel',
link: function (scope, element, attrs, ctrl) {
element.bind("keydown keypress", function (event) {
function isKeyEnterAndValid(){
return event.which === 13 && ctrl.$valid;
}
function nextItem(div, tag){
var next = div.next(tag);
if (!next) return nextItem(div, 'label');
return next;
}
function isTypeTextOrNumber(input){
return ['text', 'number'].indexOf(input.attr('type')) === -1;
}
function findInput(div){
var next = nextItem(div, 'div');
if (!next) return;
var input = next.find('input');
if (!input || isTypeTextOrNumber(input)){
return findInput(next);
}
return input[0];
}
if(isKeyEnterAndValid()) {
var nextInput = findInput(element.parent());
if(angular.isDefined(nextInput)){
event.preventDefault();
nextInput.focus();
}
}
});
}
};
});

Related

angular js event enter to call a function on the element tr

I can move up and down with the keyboard keys but i cannot get the enter event correct
**Controller**
$scope.myFunction = function()
{
alert('sadsad');
alert('hi');
}
**Also i added directive like this**
.directive('ngEnter', function ()
{
restrict:'use strict';
return {
link: function (scope, elements, attrs) {
elements.bind('keydown keypress', function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
}
};
});
**Below is the view code on which i m calling directive and function**
<tr class="find"ng-repeat="busServices in " ng-click="setSelected(busServices.bus_travel_id,this.busServices,$event)" ng-class="{selectedsd:busServices.bus_travel_id === idSelected}" ng-mouseover="ShowHideBoarding($event,this.busServices,true)" ng-mouseleave="ShowHideBoarding($event,false)" ng-init="($first) ? setSelected(busServices.bus_travel_id,this.busServices) : ''" ng-enter="myFunction()"></tr>
ng-enter doesnt get called at all.
what can i do so that ng enter can work.
Try something like this
.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keypress", function (event) {
if(event.keyCode === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
The restrict option can be omitted.
EDIT: You will have to add an attribute to your <tr> so that they can be focussed and receive key* events:
<tr tabindex="0" ....>
Replace 0 with other numbers for more of your focussable <tr>

Same AngularJS custom directive on same page

I have a custom search directive and need to use multiple instances of it on the same page. The page makes use of bootstrap tabs and there will be an instance of this search component in each tab.
The issue is that the search directive in the second tab is overriding the callback of the search directive in the first tab. Here is a snippet of my search directive:
class SearchDirective {
constructor($timeout) {
this.require = '^ngModel';
this.restrict= "AE";
this.$timeout = $timeout;
this.scope = {
ngModel: '=',
searchTime: '=',
searchCallback: '&'
};
}
compile(tElem, tAttrs) {
return this.link.bind(this);
}
link(scope, element, attrs) {
this.scope = scope;
var timer = null;
scope.$watch('ngModel', (value, preValue) => {
if (value === preValue) return;
if (timer) {
this.$timeout.cancel(timer);
}
timer = this.$timeout(() => {
timer = null;
if (value.length === 0) {
this.scope.searchCallback();
}
}, this.scope.searchTime)
});
}
}
And here is a snippet of the HTML for the search component on the first tab:
<input search search-callback="searchWindowsController.searchPcs()" search-time="600" data-ng-model="searchWindowsController.searchQuery" type="text" class="searchBox" placeholder="Search Windows...">
And this is what i have in the second tab:
<input search search-callback="searchMacController.searchPcs()" search-time="600" data-ng-model="searchMacController.searchQuery" type="text" class="searchBox" placeholder="Search Macs...">
For some reason when you search using the Windows search, it is calling the Mac callback. Can someone point me to what I am doing wrong? I am new to custom directives.
The error due to this within the $timeout function.
See live example on jsfiddle.
'use strict'
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope, $log) {
$scope.callback1 = function(){
console.log('callback1');
};
$scope.callback2 = function(){
console.log('callback2');
};
})
.directive('search',function($timeout){
return new SearchDirective($timeout);
});
class SearchDirective {
constructor(timeout) {
this.require = '^ngModel';
this.restrict = "AE";
this.$timeout = timeout;
this.scope = {
ngModel: '=',
searchTime: '=',
searchCallback: '&'
};
}
compile(tElem, tAttrs) {
return this.link.bind(this);
}
link(scope, element, attrs) {
this.scope = scope;
var timer = null;
scope.$watch('ngModel', (value, preValue) => {
if (value === preValue) return;
if (timer) {
this.$timeout.cancel(timer);
}
timer = this.$timeout(() => {
timer = null;
if (value.length === 0) {
scope.searchCallback();
}
}, scope.searchTime)
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input search search-callback="callback1()" search-time="600" data-ng-model="searchQuery1" type="text" class="searchBox" placeholder="Search Mac...">
<input search search-callback="callback2()" search-time="600" data-ng-model="searchQuery2" type="text" class="searchBox" placeholder="Search Windows...">
</div>
</div>

Angularjs : directive not called

I have an input :
<table>
...
<td style="width:59px"><input ng-model="myModel.propertyOne" ng-blur="persistValue(myModel)" enter-as-tab></td>
<td style="width:59px"><input ng-model="myModel.propertyTwo" ng-blur="persistValue(myModel)" enter-as-tab></td>
with its directive
angular.module('bioandbioApp').directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
event.preventDefault();
var elementToFocus = element.next('td').find('input')[1];
if(angular.isDefined(elementToFocus))
elementToFocus.focus();
}
});
};
});
This directive is called when user press Enter, and set the focus to the input of the next tag.
My problem is that the directive is called but elementToFocus.focus() is not called so the focus is not set.
I don't understand why. Does anyone know?
http://jsfiddle.net/tomy29/vpaqt29d/105/
Thanks
You need to find the parent first and then go the next element.
Try the below code:
var app = angular.module("ap", []);
app.controller("con", function ($scope) {
$scope.persons = [{
name: 'Susan',
age: 1
}, {
name: 'Peter',
age: 1
}, {
name: 'Jack',
age: 2
}];
});
app.directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if (event.which === 13) {
event.preventDefault();
console.log("parent");
console.log(element.parent())
var elementToFocus = element.parent().next('td').find('input')[0];
console.log("next element");
console.log(elementToFocus);
if (angular.isDefined(elementToFocus)) elementToFocus.focus();
}
});
};
});
JSFiddle
--EDIT--
This definitely can be optimized but a crud way of doing is as below:
<body ng-app="ap" ng-controller="con">
<h4>Pressing <i>Enter/Return</i> in the <i>Age</i> field will iterate through the ages</h4>
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr ng-repeat='person in persons'>
<td>
<input type='text' name="personName" ng-model="person.name" enter-as-tab />
</td>
<td>
<input type='number' name="personAge" ng-model="person.age" enter-as-tab/>
</td>
</tr>
</table>
</body>
var app = angular.module("ap", []);
app.controller("con", function ($scope) {
$scope.persons = [{
name: 'Susan',
age: 1
}, {
name: 'Peter',
age: 1
}, {
name: 'Jack',
age: 2
}];
});
app.directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if (event.which === 13) {
event.preventDefault();
console.log("parent");
console.log(element.parent())
var elementToFocus = element.parent().next('td').find('input')[0];
console.log('next element');
console.log(elementToFocus);
if (angular.isDefined(elementToFocus)) {
elementToFocus.focus();
} else {
element.parent().parent().next('tr').find('input')[0].focus();
}
}
});
};
});
JSFiddle
you can use this code
$(this).closest('td').next('td').find('input')[0];
Currently, this is the input field of the current td. So, by that, you can find the input to the next of the current td.
Here is the working JSFIDDLE
Although your fiddle is using static contents and a fixed set of fields, below is how I achieved the solution, however, depending on the dynamic content and your requirements, the solution may vary:
app.directive('enterAsTab', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
event.preventDefault();
var elementToFocus;
if(attrs["name"] == "personAge"){
elementToFocus = element.parent().parent().next().find('input')[0];
}else{
elementToFocus = element.parent().next().find('input')[0];
}
if(angular.isDefined(elementToFocus))
elementToFocus.focus();
}
});
};
});

Can I simplify clicking the enter key with AngularJS?

I already have this code that I came up with:
In my outer controller:
$scope.key = function ($event) {
$scope.$broadcast('key', $event.keyCode)
}
In my inner controller (I have more than one like this)
$scope.$on('key', function (e, key) {
if (key == 13) {
if (ts.test.current) {
var btn = null;
if (ts.test.userTestId) {
btn = document.getElementById('viewQuestions');
} else {
btn = document.getElementById('acquireTest');
}
$timeout(function () {
btn.focus();
btn.click();
window.setTimeout(function () {
btn.blur();
}, 500);
})
}
}
});
Is there another way that I could simplify this using some features of AngularJS that I have not included here?
Please check this gist, https://gist.github.com/EpokK/5884263
You can simply create a directive ng-enter and pass your action as paramater
app.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
HTML
<input ng-enter="myControllerFunction()" />
You may change the name ng-enter to something different, because ng-** is a reserved by Angular core team.
Also I see that your controller is dealing with DOM, and you should not. Move those logic to other directive or to HTML, and keep your controller lean.
if (ts.test.userTestId) {
btn = document.getElementById('viewQuestions'); //NOT in controller
} else {
btn = document.getElementById('acquireTest'); //NOT in controller
}
$timeout(function () {
btn.focus(); //NOT in controller
btn.click(); //NOT in controller
window.setTimeout(function () { // $timeout in $timeout, questionable
btn.blur(); //NOT in controller
}, 500);
})
What i've done in the past is a directive which just listens for enter key inputs and then executes a function that is provided to it similar to an ng-click. This makes the logic stay in the controller, and will allow for reuse across multiple elements.
//directive
angular.module('yourModule').directive('enterHandler', [function () {
return{
restrict:'A',
link: function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
var key = event.which ? event.which : event.keyCode;
if (key === 13) {
scope.$apply(function () {
scope.$eval(attrs.enterHandler);
});
event.preventDefault();
}
});
}
}
}]);
then your controller becomes
$scope.eventHandler = function(){
if (ts.test.current) {
var btn = ts.test.userTestId
? document.getElementById('viewQuestions')
: document.getElementById('acquireTest');
$timeout(function () {
btn.focus();
btn.click();
window.setTimeout(function () {
btn.blur();
}, 500);
})
}
}
and your markup can then be
<div enter-handler="eventHandler()" ></div>

how to exec js after AngularJS ng-repeat finished

I'm new to AngularJS. I want to use ng-repeat to render a list of data.
Each of the data should have a <abbr class="timeago" title="2012-10-10 05:47:21"></abbr> alike after rendered. And then I could use jquery plugin timeago to turn it into human friendly text about 1 hour ago.
My code is as below. But it take no effect. Please help.
EDIT: My problem is that, I can get the right html rendered. But code in directive do not run.
the html:
<div ng-app='weiboApp' ng-controller="WeiboListCtrl">
<table><tbody>
<tr ng-repeat='w in weibo' weiboLister='w'>
<td>{{ w.text }}></td>
<td><abbr class="timeago" title="{{ w.created }}"></abbr></td>
</tr>
</tbody></table>
</div>
the js:
var module = angular
.module('weiboApp', [])
.directive('weiboLister', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.watch('w', function (val) {
console.log(element); //this never run
element.find("abbr.timeago").timeago(); //this never run
}, true);
}
}
});
function WeiboListCtrl($scope, $http) {
$http.get('/api/weibo/list').success(function(data) {
$scope.weibo = data;
});
}
The problem turned out to be: should define directive with camel-case weiboLister and use it in html with snake-case weibo-lister. Thanks to #tosh shimayama.
The correct code as below: (I added a remove function in case you're looking for the same thing.)
the html:
<div ng-app='weiboApp' ng-controller="WeiboListCtrl">
<table><tbody>
<tr ng-repeat='w in weibo' weibo-lister='w'> <!--important to be snake-case here-->
<td>{{ w.text }}></td>
<td><abbr class="timeago" title="{{ w.created }}"></abbr></td>
<td><a ng-click='remove(w)'>×</a></td>
</tr>
</tbody></table>
</div>
the js:
var module = angular
.module('weiboApp', [])
.directive('weiboLister', function () {
function delete(id, s_function, f_function) {
//...
if success { s_function(); }
else { f_function(); }
}
return {
restrict: 'A',
link: function (scope, element, attr) {
scope.$watch('w', function (val) {
element.find("abbr.timeago").timeago();
}
scope.destroy = function(callback) {
deletenews(scope.w.id, function () {
//s_function, things you want to do when delete with success
element.fadeOut(400, function () {
//this callback will splice the element in model
if (callback) callback.apply(scope);
})
}, function () {
//f_function, when delete with failure
});
};
}
}
});
function WeiboListCtrl($scope, $http) {
$http.get('/api/weibo/list').success(function(data) {
$scope.weibo = data;
});
$scope.removeWeibo = function(w) {
var idx = $scope.weibo.indexOf(w);
if (idx !== -1) {
this.destroy(function() {
$scope.weibo.splice(idx, 1);
});
}
};
}

Resources