Angularjs : directive not called - angularjs

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

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>

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>

Issue with Popover AngularJS

I have a bunch of table rows which include inputs and buttons, namely. I would like to have a Popover display to the right of an input for a row if the value isn't matching the requirements defined. The button will also be disabled until the value of the input is correct.
Relevant HTML:
<div class="row col-md-4">
<table ng-controller="TestController" style="width: 100%">
<tr ng-repeat="element in model.InvoiceNumbers">
<td><input ng-model="element.id"
popover="Invoice must match ##-####!"
popover-placement="right"
popover-trigger="{{ { false: 'manual', true: 'blur'}[!isValidInvoice(element.id)] }}"
popover-title="{{element.id}}"/></td>
<td>{{element.id}}</td>
<td><button ng-disabled="!isValidInvoice(element.id)">Approve</button></td>
</tr>
</table>
</div>
Relevant JavaScript:
app.controller("TestController", function ($scope) {
$scope.model = {
InvoiceNumbers : [
{ id: '12-1234' },
{ id: '12-1235' },
{ id: '1234567' },
{ id: '1' },
{ id: '' }],
};
$scope.isValidInvoice = function (invoice) {
if (invoice == null) return false;
if (invoice.length != 7) return false;
if (invoice.search('[0-9]{2}-[0-9]{4}') == -1) return false;
return true;
};
});
The button gets disabled correctly on my local solution. However, I can't get the Popover to work; it behaves as if the model in its scope isn't getting updated. So, I looked through several links here (though most were from 2013 so I'd imagine a bit has changed) and their problems seemed to be solved by removing primitive binding. That didn't fix anything here. I added some console.log() lines in the function getting called from the Popover, and it was getting the correct value from the model each time. I also added a title to the Popover to show that its seeing the right value from the model.After seeing the log showing that it should be working correctly, I've run out of ideas.
The issue is element.id isn't updating dynamically within the trigger (it keeps its initial value, unlike popover-title which updates with the model). Is there something I did wrong?
Also, I've only been working with angular for a day so if you all have any suggestions on better ways to accomplish this, I'm open to suggestions.
Plunker: http://plnkr.co/edit/tiooSxSDgzXhbmIty3Kc?p=preview
Thanks
Found a solution on the angular-ui github page that involved adding these directives:
.directive( 'popPopup', function () {
return {
restrict: 'EA',
replace: true,
scope: { title: '#', content: '#', placement: '#', animation: '&', isOpen: '&' },
templateUrl: 'template/popover/popover.html'
};
})
.directive('pop', function($tooltip, $timeout) {
var tooltip = $tooltip('pop', 'pop', 'event');
var compile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentCompile = compile(element, attrs);
return function(scope, element, attrs ) {
var first = true;
attrs.$observe('popShow', function (val) {
if (JSON.parse(!first || val || false)) {
$timeout(function () {
element.triggerHandler('event');
});
}
first = false;
});
parentCompile(scope, element, attrs);
}
};
return tooltip;
});
And here's the changes I made to the controller and view to make it work like I wanted in the original question:
<div class="row col-md-4">
<table ng-controller="TestController" style="width: 100%">
<tr ng-repeat="element in model.InvoiceNumbers">
<td><input ng-model="element.id"
pop="Invoice must match ##-####!"
pop-placement="right"
pop-show="{{element.showPop}}"
ng-blur="isValidInvoice($index, $event)" /></td>
<td>{{element.id}}</td>
<td><button ng-disabled="!isValidInvoice($index)">Approve</button></td>
</tr>
</table>
</div>
JavaScript:
app.controller("TestController", function ($scope) {
$scope.model = {
InvoiceNumbers: [
{ id: '12-1234', showPop: false },
{ id: '12-1235', showPop: false },
{ id: '1234567', showPop: false },
{ id: '1', showPop: false },
{ id: '', showPop: false }]
};
$scope.isValidInvoice = function ($index, $event) {
var obj = $scope.model.InvoiceNumbers[$index];
var isValid = function () {
if (obj.id === null) return false;
if (obj.id.length != 7) return false;
if (obj.id.search('[0-9]{2}-[0-9]{4}') == -1) return false;
return true;
};
if ($event != null && $event.type == "blur") obj.showPop = !isValid();
return isValid();
};
});
Plunker: http://plnkr.co/edit/5m6LHbapxp5jqk8jANR2?p=preview

Using the enter key as tab using only angularjs and jqlite

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

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