I'm developing a upvote/downvote controlling system for a dynamic bunch of cards.
I can controll if I click to the img the checked = true and checked = false value but The problem I've found and because my code doesn't work as expected is I can't update my value in the ng-model, so the next time the function is called I receive the same value. As well, I can't update and show correctly the new value. As well, the only card that works is the first one (it's not dynamic)
All in which I've been working can be found in this plunk.
As a very new angular guy, I tried to investigate and read as much as possible but I'm not even 100% sure this is the right way, so I'm totally open for other ways of doing this, attending to performance and clean code. Here bellow I paste what I've actually achieved:
index.html
<card-interactions class="card-element" ng-repeat="element in myIndex.feed">
<label class="rating-upvote">
<input type="checkbox" ng-click="rate('upvote', u[element.id])" ng-true-value="1" ng-false-value="0" ng-model="u[element.id]" ng-init="element.user_actions.voted === 'upvoted' ? u[element.id] = 1 : u[element.id] = 0" />
<ng-include src="'upvote.svg'"></ng-include>
{{ element.upvotes + u[1] }}
</label>
<label class="rating-downvote">
<input type="checkbox" ng-click="rate('downvote', d[element.id])" ng-model="d[element.id]" ng-true-value="1" ng-false-value="0" ng-init="element.user_actions.voted === 'downvoted' ? d[element.id] = 1 : d[element.id] = 0" />
<ng-include src="'downvote.svg'"></ng-include>
{{ element.downvotes + d[1] }}
</label>
<hr>
</card-interactions>
index.js
'use strict';
var index = angular.module('app.index', ['index.factory']);
index.controller('indexController', ['indexFactory', function (indexFactory) {
var data = this;
data.functions = {
getFeed: function () {
indexFactory.getJSON(function (response) {
data.feed = response.index;
});
}
};
this.functions.getFeed();
}
]);
index.directive('cardInteractions', [ function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
scope.rate = function(action, value) {
var check_up = element.find('input')[0];
var check_down = element.find('input')[1];
if (action === 'upvote') {
if (check_down.checked === true) {
check_down.checked = false;
}
} else {
if (action === 'downvote') {
if (check_up.checked === true) {
check_up.checked = false;
}
}
}
}
}
};
}]);
Hope you guys can help me with this.
Every contribution is appreciated.
Thanks in advice.
I have updated your directive in this plunker,
https://plnkr.co/edit/HvcBv8XavnDZTlTeFntv?p=preview
index.directive('cardInteractions', [ function () {
return {
restrict: 'E',
scope: {
vote: '='
},
templateUrl: 'vote.html',
link: function (scope, element, attrs) {
scope.vote.upValue = scope.vote.downValue = 0;
if(scope.vote.user_actions.voted) {
switch(scope.vote.user_actions.voted) {
case 'upvoted':
scope.vote.upValue = 1;
break;
case 'downvoted':
scope.vote.downValue = 1;
break;
}
}
scope.upVote = function() {
if(scope.vote.downValue === 1) {
scope.vote.downValue = 0;
scope.vote.downvotes--;
}
if(scope.vote.upValue === 1) {
scope.vote.upvotes++;
} else {
scope.vote.upvotes--;
}
};
scope.downVote = function() {
if(scope.vote.upValue === 1) {
scope.vote.upValue = 0;
scope.vote.upvotes--;
}
if(scope.vote.downValue === 1) {
scope.vote.downvotes++;
} else {
scope.vote.downvotes--;
}
};
}
};
Related
I am a newbie struggling with Angular JS.
I am trying to make a questionaire and have the following code but keep getting the feedback for incorrect no matter what I try.
I have assigned a numeric value to each of four buttons, the titles of which are being pulled through from an array. I want to check the value of the selected button against the correct answer in the array. Can someone tell me where I am going wrong?
HTML
<div id="ansblock">
<button type="button" ng-click="myAnswer=0">{{options [0]}} </button>
<button type="button" ng-click="myAnswer=1">{{options [1]}}</button>
<button type="button" ng-click="myAnswer=2">{{options [2]}}</button>
<button type="button" ng-click="myAnswer=3">{{options [3]}}</button>
<p>Test to show button click value working {{myAnswer}}</p>
</div>
<button ng-click="checkAnswer()">Submit</button>
<span ng-show="correctAns">That is correct!</span>
<span ng-show="!correctAns">Sorry, that is an incorrect answer.</span>
JS:
var app = angular.module('quizApp', []);
app.directive('quiz', function (quizFactory) {
return {
restrict: 'AE',
scope: {},
templateUrl: 'template.html',
link: function (scope, elem, attrs) {
scope.start = function () {
scope.id = 0;
scope.quizOver = false;
scope.inProgress = true;
scope.getQuestion();
};
scope.reset = function () {
scope.inProgress = false;
scope.score = 0;
}
scope.getQuestion = function () {
var q = quizFactory.getQuestion(scope.id);
if (q) {
scope.question = q.question;
scope.options = q.options;
scope.answer = q.answer;
scope.answerMode = true;
} else {
scope.quizOver = true;
}
};
scope.checkAnswer = function () {
var ans = $('scope.myAnswer').val();
if(ans==scope.options[scope.answer]) {
scope.score++;
scope.correctAns = true;
} else {
scope.correctAns = false;
}
scope.answerMode = false;
};
scope.nextQuestion = function () {
scope.id++;
scope.getQuestion();
}
scope.reset();
}
}
});
app.factory('quizFactory', function () {
var questions = [
{
question: "Which of the following is a WW2 fighter?",
options: ["Albatross", "Concorde", "Spitfire", "Mirage"],
answer: 2
},
{
question: "When was the Great Fire of London?",
options: ["1666", "1966", "1844", "1235"],
answer: 0
},
{
question: "Which group is a Swedish pop band?",
options: ["Eagles", "Led Zeppelin", "Beatles", "Abba"],
answer: 3
},
{
question: "Which city is the capita of Australia?",
options: ["Canberra", "Sydney", "Melbourne", "Hobart"],
answer: 0
},
{
question: "Which brand does not make motorcycles?",
options: ["BMW", "Mercedes", "Kawasaki", "Honda"],
answer: 1
}
];
return {
getQuestion: function (id) {
if (id < questions.length) {
return questions[id];
} else {
return false;
}
}
};
});
I'm quite new to AngularJS and struggling a bit to have some input fields updated after an autocompletion event using google maps.
The idea is that when the user inputs his city/zip code, I would update 3 fields which are themselves linked to an object.
So far, I managed to have a working code except that sometimes the fields are not updated immediately : I have to autocomplete twice so that the good value will appear in the fields.
I've tweaked an existing angular directive in order to get what I want but since this is new to me, I dont know if I'm using the correct approach.
Below is the JS directive I use :
angular.module( "ngVilleAutocomplete", [])
.directive('ngAutocomplete', function($parse) {
return {
scope: {
details: '=',
ngAutocomplete: '=',
options: '=',
data: '='
},
link: function(scope, element, attrs, model) {
//options for autocomplete
var opts
//convert options provided to opts
var initOpts = function() {
opts = {}
if (scope.options) {
if (scope.options.types) {
opts.types = []
opts.types.push(scope.options.types)
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
}
}
}
}
initOpts()
//create new autocomplete
//reinitializes on every change of the options provided
var newAutocomplete = function() {
scope.gPlace = new google.maps.places.Autocomplete(element[0], opts);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.details = scope.gPlace.getPlace();
//console.log(scope.details)
var HasCP = false;
for (var i=0 ; i<scope.details.address_components.length ; i++){
for (var j=0 ; j<scope.details.address_components[i].types.length ; j++){
if (scope.details.address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = scope.details.address_components[i].long_name;
HasCP = true;
} else if (scope.details.address_components[i].types[j] == 'locality' && scope.data.Ville != 'undefined') {
scope.data.Ville = scope.details.address_components[i].long_name;
} else if (scope.details.address_components[i].types[j] == 'country' && scope.data.Pays != 'undefined') {
scope.data.Pays = scope.details.address_components[i].long_name;
}
}
}
if (!HasCP){
var latlng = {lat: scope.details.geometry.location.lat(), lng: scope.details.geometry.location.lng()};
var geocoder = new google.maps.Geocoder;
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i=0 ; i<results[0].address_components.length ; i++){
for (var j=0 ; j<results[0].address_components[i].types.length ; j++){
if (results[0].address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = results[0].address_components[i].long_name;
console.log('pc trouvé :' + scope.data.CP);
}
}
}
}
});
}
//console.log(scope.data)
scope.ngAutocomplete = element.val();
});
})
}
newAutocomplete()
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
newAutocomplete()
element[0].value = '';
scope.ngAutocomplete = element.val();
}, true);
}
};
});
The matching HTML code is below :
<div class="form-group">
<lable>Code postal : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Code postal" ng-model="client.CP" />
</div>
<div class="form-group">
<lable>Ville : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Ville" ng-model="client.Ville" />
</div>
<div class="form-group">
<lable>Pays : </label>
<input type="text" class="form-control" name="Pays" ng-model="client.Pays" placeholder="Pays" />
</div>
You'll see that I pass the "client" object directly to my directive which then updates this object. I expected angular to update the html page as soon as the values of the client object are updated but I will not always be the case :
If I search twice the same city, the values are not updated
If I search a city, Google wont send me a zip code so I have to do another request to the geocoding service and I get the zipcode in return but while my client.CP field is correctly updated, changes are not visible in the CP input field until I do another search.
Thanks in advance for any advice on what I'm doing wrong.
I am working on one requirement where I want to allow only even numbers to text box or number box(input type number). with minimum and maximum limit like from 4 to 14 and it should only increase by step of 2 if we have number box.
I tried with HTML input type number with min max and step attributes it's working fine but we can edit the text box with any number so to restrict I tried using directive but it's not working out for me. I will be glad if anyone can help me out with this.
HTML :
<body ng-controller="ctrl">
new : <number-only-input step="2" min="4" max="14" input-value="wks.number" input-name="wks.name" >
</body>
Script :
var app = angular.module('app', []);
app.controller('ctrl', function($scope){
$scope.name = 'Samir Shah';
$scope.price = -10;
$scope.wks = {number: '', name: 'testing'};
});
app.directive('numberOnlyInput', function () {
return {
restrict: 'EA',
template: '<input type="text" name="{{inputName}}" ng-model="inputValue" />',
scope: {
inputValue: '=',
inputName: '=',
min: '#',
max: '#',
step: '#'
},
link: function (scope) {
scope.$watch('inputValue', function(newValue,oldValue) {
var arr = String(newValue).split("");
if (arr.length === 0) return;
if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.' )) return;
if (arr.length === 2 && newValue === '-.') return;
if (isNaN(newValue)) {
scope.inputValue = oldValue;
return;
}
if(!isNaN(newValue)){
if(newValue < parseInt(scope.min) || newValue > parseInt(scope.max)){
scope.inputValue = oldValue;
return;
}
}
});
}
};
});
<form name="testForm">
<div ng-controller="MyCtrl">
<input type="text" name="testInput" ng-model="number" ng-min="2" ng-max="14" required="required" numbers-only="numbers-only" />
<div ng-show="testForm.testInput.$error.nonnumeric" style="color: red;">
Numeric input only.
</div>
<div ng-show="testForm.testInput.$error.belowminimum" style="color: red;">
Number is too small.
</div>
<div ng-show="testForm.testInput.$error.abovemaximum" style="color: red;">
Number is too big.
</div>
<div ng-show="testForm.testInput.$error.odd" style="color: red;">
Numeric is odd.
</div>
</div>
</form>
angular.module('myApp', []).directive('numbersOnly', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, modelCtrl) {
element.bind('blur', function () {
if (parseInt(element.val(), 10) < attrs.ngMin) {
modelCtrl.$setValidity('belowminimum', false);
scope.$apply(function () {
element.val('');
});
}
});
modelCtrl.$parsers.push(function (inputValue) {
// this next if is necessary for when using ng-required on your input.
// In such cases, when a letter is typed first, this parser will be called
// again, and the 2nd time, the value will be undefined
if (inputValue == undefined) return ''
var transformedInput = inputValue.replace(/[^0-9]/g, '');
if (transformedInput != inputValue || (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') || parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10) || (transformedInput % 2 !== 0 && transformedInput !== '1')) {
if (transformedInput != inputValue) {
modelCtrl.$setValidity('nonnumeric', false);
} else {
modelCtrl.$setValidity('nonnumeric', true);
}
if (parseInt(transformedInput, 10) < parseInt(attrs.ngMin, 10) && transformedInput !== '1') {
modelCtrl.$setValidity('belowminimum', false);
} else {
modelCtrl.$setValidity('belowminimum', true);
}
if (parseInt(transformedInput, 10) > parseInt(attrs.ngMax, 10)) {
modelCtrl.$setValidity('abovemaximum', false);
} else {
modelCtrl.$setValidity('abovemaximum', true);
}
if (transformedInput % 2 !== 0 && transformedInput !== '1') {
modelCtrl.$setValidity('odd', false);
} else {
modelCtrl.$setValidity('odd', true);
}
transformedInput = '';
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
return transformedInput;
}
modelCtrl.$setValidity('nonnumeric', true);
modelCtrl.$setValidity('belowminimum', true);
modelCtrl.$setValidity('abovemaximum', true);
modelCtrl.$setValidity('odd', true);
return transformedInput;
});
}
};
});
Active fiddle http://jsfiddle.net/tuckerjt07/1Ldmkmog/
You could define a property with getter and setter to process the entered value. If the value does not match the requrements display messages but not accept new value.
Using this method you could apply any validation logic, the second field editValue is needed because otherwise you could not enter an invalid number. Therefore editValue alows to enter numbers with numerous digits which will be partially invalid during entering the value.
Property:
// Property used to bind input containing validation
Object.defineProperty($scope, "number", {
get: function() {
return $scope.editValue;
},
set: function(value) {
value = parseInt(value);
$scope.editValue = value;
var isValid = true;
// Min?
if (value < parseInt($scope.min)) {
$scope.toSmall = true;
isValid = false;
} else {
$scope.toSmall = false;
}
// Max?
if (value > parseInt($scope.max)) {
$scope.toBig = true;
isValid = false;
} else {
$scope.toBig = false;
}
// Step not valid
if (value % parseInt($scope.step) > 0) {
$scope.stepNotValid = true;
isValid = false;
} else {
$scope.stepNotValid = false;
}
$scope.isValid = isValid;
if (isValid) {
$scope.value = value;
}
}
});
Working example
Below you can find a complete working example directive containing the property described above including increase/decrease buttons:
var app = angular.module('myApp', []);
app.directive('numberOnlyInput', function() {
return {
restrict: 'E',
template: '<input type="text" ng-model="number" ng-class="{\'error\': !isValid}"/><button ng-click="increase()">+</button><button ng-click="decrease()">-</button> Value: {{value}} {{stepNotValid ? (" value must be in steps of " + step) : ""}} {{toSmall ? " value must be greater or equal to " + min : ""}} {{toBig ? " value must be smaler or equal to " + max : ""}}',
scope: {
value: '=value',
min: '#',
max: '#',
step: '#'
},
link: function($scope) {
// Increase value
$scope.increase = function() {
var newValue = parseInt($scope.value) + parseInt($scope.step);
if (newValue <= $scope.max) {
$scope.number = newValue;
$scope.editValue = $scope.number;
}
};
// Decrease value
$scope.decrease = function() {
var newValue = parseInt($scope.value) - parseInt($scope.step);
if (newValue >= $scope.min) {
$scope.number = newValue;
$scope.editValue = $scope.number;
}
};
// Property used to bind input containing validation
Object.defineProperty($scope, "number", {
get: function() {
return $scope.editValue;
},
set: function(value) {
value = parseInt(value);
$scope.editValue = value;
var isValid = true;
// Min?
if (value < parseInt($scope.min)) {
$scope.toSmall = true;
isValid = false;
} else {
$scope.toSmall = false;
}
// Max?
if (value > parseInt($scope.max)) {
$scope.toBig = true;
isValid = false;
} else {
$scope.toBig = false;
}
// Step not valid
if (value % parseInt($scope.step) > 0) {
$scope.stepNotValid = true;
isValid = false;
} else {
$scope.stepNotValid = false;
}
$scope.isValid = isValid;
if (isValid) {
$scope.value = value;
}
}
});
// Init actual Value of the input element
$scope.number = parseInt($scope.value);
$scope.editValue = parseInt($scope.value);
}
};
});
app.controller('controller', function($scope) {
$scope.value = 10;
});
.error {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="controller">
Number:
<number-only-input min="4" max="14" step="2" value="value"></number-only-input>
</div>
Why are you doing too much of work of a simple thing. Max length will not work with <input type="number" the best way I know is to use oninput event to limit the maxlength. Please see the below code, Its a generic solution work with all the Javascript framework.
<input name="somename"
oninput="javascript: if (this.value.length > this.maxLength) this.value = this.value.slice(0, this.maxLength);"
type = "number"
maxlength = "6"
/>
I have a customers.create.html partial bound to the WizardController.
Then I have 3 customers.create1,2,3.html partial files bound to WizardController1,2,3
Each WizardController1,2 or 3 has an isValid() function. This function determines wether the user can proceed to the next step.
The next button at the bottom of the pasted html should be disabed if ALL ? isValid() functions are false...
Thats my question but the same time that seems not correct to me.
I guess I am not doing the Wizard correctly...
Can someone please guide me how I should proceed with the architecture that the bottom next button is disabled when the current step isValid function returns false, please.
How can I make a connection from the WizardController to any of the WizardController1,2 or 3 ?
Is Firing an event like broadcast a good direction?
<div class="btn-group">
<button class="btn" ng-class="{'btn-primary':isCurrentStep(0)}" ng-click="setCurrentStep(0)">One</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(1)}" ng-click="setCurrentStep(1)">Two</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(2)}" ng-click="setCurrentStep(2)">Three</button>
</div>
<div ng-switch="getCurrentStep()" ng-animate="'slide'" class="slide-frame">
<div ng-switch-when="one">
<div ng-controller="WizardController1" ng-include src="'../views/customers.create1.html'"></div>
</div>
<div ng-switch-when="two">
<div ng-controller="WizardController2" ng-include src="'../views/customers.create2.html'"></div>
</div>
<div ng-switch-when="three">
<div ng-controller="WizardController3" ng-include src="'../views/customers.create3.html'"></div>
</div>
</div>
<a class="btn" ng-click="handlePrevious()" ng-show="!isFirstStep()">Back</a>
<a class="btn btn-primary" ng-disabled="" ng-click="handleNext(dismiss)">{{getNextLabel()}}</a>
'use strict';
angular.module('myApp').controller('WizardController', function($scope) {
$scope.steps = ['one', 'two', 'three'];
$scope.step = 0;
$scope.wizard = { tacos: 2 };
$scope.isFirstStep = function() {
return $scope.step === 0;
};
$scope.isLastStep = function() {
return $scope.step === ($scope.steps.length - 1);
};
$scope.isCurrentStep = function(step) {
return $scope.step === step;
};
$scope.setCurrentStep = function(step) {
$scope.step = step;
};
$scope.getCurrentStep = function() {
return $scope.steps[$scope.step];
};
$scope.getNextLabel = function() {
return ($scope.isLastStep()) ? 'Submit' : 'Next';
};
$scope.handlePrevious = function() {
$scope.step -= ($scope.isFirstStep()) ? 0 : 1;
};
$scope.handleNext = function(dismiss) {
if($scope.isLastStep()) {
dismiss();
} else {
$scope.step += 1;
}
};
});
durandalJS wizard sample code which could be used to rewrite a wizard for angularJS:
define(['durandal/activator', 'viewmodels/step1', 'viewmodels/step2', 'knockout', 'plugins/dialog', 'durandal/app', 'services/dataservice'],
function (activator, Step1, Step2, ko, dialog, app, service) {
var ctor = function (viewMode, schoolyearId) {
debugger;
if (viewMode === 'edit') {
service.editSchoolyear(schoolyearId);
}
else if (viewMode === 'create') {
service.createSchoolyear();
}
var self = this;
var steps = [new Step1(), new Step2()];
var step = ko.observable(0); // Start with first step
self.activeStep = activator.create();
var stepsLength = steps.length;
this.hasPrevious = ko.computed(function () {
return step() > 0;
});
self.caption = ko.observable();
this.activeStep(steps[step()]);
this.hasNext = ko.computed(function () {
if ((step() === stepsLength - 1) && self.activeStep().isValid()) {
// save
self.caption('save');
return true;
} else if ((step() < stepsLength - 1) && self.activeStep().isValid()) {
self.caption('next');
return true;
}
});
this.isLastStep = function() {
return step() === stepsLength - 1;
}
this.next = function() {
if (this.isLastStep()) {
$.when(service.createTimeTable())
.done(function () {
app.trigger('savedTimeTable', { isSuccess: true });
})
.fail(function () {
app.trigger('savedTimeTable', { isSuccess: false });
});
}
else if (step() < stepsLength) {
step(step() + 1);
self.activeStep(steps[step()]);
}
}
this.previous = function() {
if (step() > 0) {
step(step() - 1);
self.activeStep(steps[step()]);
}
}
}
return ctor;
});
What is the best way, when hitting enter inside a form, the focus to go to the next input instead submitting the form with angularjs.
I have a form with a lot of fields and customers are used to hit enter to move to the next input (comming from desktop applications). The angularjs saves the form when the user hits enter. I like to change this. Is it possible ?
I suggest making a custom directive. Something like this. I haven't tested this.
.directive('focus', function() {
return {
restrict: 'A',
link: function($scope,elem,attrs) {
elem.bind('keydown', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
elem.next().focus();
}
});
}
}
});
Something like that should work. You might have to tweek something. Good luck.
Create a custom directive:
.directive('nextOnEnter', function () {
return {
restrict: 'A',
link: function ($scope, selem, attrs) {
selem.bind('keydown', function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
var pageElems = document.querySelectorAll('input, select, textarea'),
elem = e.srcElement || e.target,
focusNext = false,
len = pageElems.length;
for (var i = 0; i < len; i++) {
var pe = pageElems[i];
if (focusNext) {
if (pe.style.display !== 'none') {
angular.element(pe).focus();
break;
}
} else if (pe === elem) {
focusNext = true;
}
}
}
});
}
}
})
This is the directive I ended up with (thanks to Zack Argyle):
angular.module('myApp').directive("nextFocus", nextFocus);
/** Usage:
<input next-focus id="field1">
<input next-focus id="field2">
<input id="field3">
Upon pressing ENTER key the directive will switch focus to
the next field id e.g field2
The last field should not have next-focus directive to avoid
focusing on non-existing element.
Works for Web, iOS (Go button) & Android (Next button) browsers,
**/
function nextFocus() {
var directive = {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind('keydown', function(e) {
var partsId = attrs.id.match(/field(\d{1})/);
var currentId = parseInt(partsId[1]);
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
document.querySelector('#field' + (currentId + 1)).focus();
}
});
}
};
return directive;
}
I tried this solution out. As advertised, it needed some tweaking. Here is what ended up working for me:
.directive("focus", function () {
return {
restrict: "A",
link: function ($scope, elem, attrs) {
var focusables = $(":focusable");
elem.bind("keydown", function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
var current = focusables.index(this);
var next = focusables.eq(current + 1).length ? focusables.eq(current + 1) : focusables.eq(0);
next.focus();
e.preventDefault();
}
});
}
}
Note that the in order to get the :focusable pseudo to work, you will need to reference JQueryUI. (the latest version 1.11.4 worked for me)
This is the directive I ended up with (thanks to Zack Argyle and Oleg):
app.directive("nextFocus", function () {
/** Usage:
<input next-focus tabindex="0" id="field1">
<input next-focus tabindex="1" id="field2">
<input id="field3">
Upon pressing ENTER key the directive will switch focus to
the next field id e.g field2
The last field should not have next-focus directive to avoid
focusing on non-existing element.
Works for Web, iOS (Go button) & Android (Next button) browsers,
**/
var directive = {
restrict: 'A',
link: function (scope, elem, attrs) {
elem.bind('keydown', function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
try {
if (attrs.tabindex != undefined) {
var currentTabIndex = attrs.tabindex;
var nextTabIndex = parseInt(attrs.tabindex) + 1;
$("[tabindex=" + nextTabIndex + "]").focus();
}
} catch (e) {
}
}
});
}
};
return directive;
});
Based on the answer by wolcy97 but using only angular
/** Usage:
<input next-focus tabindex="0">
<input next-focus tabindex="1">
<input tabindex="2">
Upon pressing ENTER key the directive will switch focus to
the next tabindex.
The last field should not have next-focus directive to avoid
focusing on non-existing element.
Works for Web, iOS (Go button) & Android (Next button) browsers,
**/
app.directive('nextFocus', [function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind('keydown', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
try {
if (attrs.tabindex !== undefined) {
var currentTabeIndex = attrs.tabindex;
var nextTabIndex = parseInt(currentTabeIndex) + 1;
var elems = document.querySelectorAll("[tabindex]");
for (var i = 0, len = elems.length; i < len; i++) {
var el = angular.element(elems[i]);
var idx = parseInt(el.attr('tabindex'));
if (idx === nextTabIndex) {
elems[i].focus();
break;
}
}
}
} catch (e) {
console.log('Focus error: ' + e);
}
}
});
}
};
}]);
Pure JavaScript Enter as TAB
angular.module('app').directive('tabNext', function () {
return {
restrict: 'A',
link: function (scope, elem) {
elem.bind('keyup', function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
var eIDX = -1;
for (var i = 0; i < this.form.elements.length; i++) {
if (elem.eq(this.form.elements[i])) {
eIDX = i;
break;
}
}
if (eIDX === -1) {
return;
}
var j = eIDX + 1;
var theform = this.form;
while (j !== eIDX) {
if (j >= theform.elements.length){
j = 0;
}
if ((theform.elements[j].type !== "hidden") && (theform.elements[j].type !== "file")
&& (theform.elements[j].name !== theform.elements[eIDX].name)
&& (! theform.elements[j].disabled)
&& (theform.elements[j].tabIndex >= 0)) {
if (theform.elements[j].type === "select-one") {
theform.elements[j].focus();
} else if (theform.elements[j].type === "button") {
theform.elements[j].focus();
} else {
theform.elements[j].focus();
theform.elements[j].select();
}
return;
break;
}
j++;
}
}
});
}
}});
<table class="table table-striped table-bordered table-hover">
<tr>
<th>S No</th>
<th>Stock Id</th>
<th>Description</th>
<th>Qty</th>
<th>UOM</th>
<th>Rate</th>
<th>Amount</th>
<th>Add</th>
<th>Delete</th>
</tr>
<tr ng-repeat="item in stockitems">
<td>{{$index + 1}}</td>
<td>
<input type="text" style="width:70px" id="stkid{{$index}}" class="form-control" name="stockid" required insert="Addnewrow();" ng-keyup="moveFocus('desc','amount','stkid','stkid',$index,$event)" ng-blur="getStockitem($index);" typeahead="a.stockitem_code as (a.stockitem_code +' | ' + a.stockitem_name +' | '+ a.rate) for a in stock | filter:$viewValue | limitTo:8" data-ng-model="item.stockid" rows="3" />
</td>
<td>
<input type="text" class="form-control" id="desc{{$index}}" name="description" ng-keyup="moveFocus('quantity','stkid','desc','desc',$index,$event)" data-ng-model="item.description" rows="3" />
</td>
<td>
<input type="text" style="width:70px" id="quantity{{$index}}" class="form-control" ng-keyup="moveFocus('uom','desc','quantity','quantity',$index,$event)" ng-change="GetAmount($index,'qty');" ng-pattern="/^\d+$/" required name="qty" data-ng-model="item.qty" rows="3" />
</td>
<td>
<input type="text" style="width:70px" id="uom{{$index}}" class="form-control" name="uom" ng-keyup="moveFocus('rate','quantity','uom','uom',$index,$event)" data-ng-model="item.uom" required rows="3" />
</td>
<td>
<input type="text" style="width:70px" id="rate{{$index}}" class="form-control" name="rate" ng-keyup="moveFocus('amount','uom','rate','rate',$index,$event)" required data-ng-model="item.rate" ng-pattern="/^\d{0,9}(\.\d{1,9})?$/" ng-change="GetAmount($index,'rate');" rows="3" />
</td>
<td>
<input type="text" style="width:70px" id="amount{{$index}}" class="form-control" ng-keyup="moveFocus('stkid','rate','amount','amount',$index,$event)" name="amount" required data-ng-model="item.amount" rows="3" />
</td>
<td><span ng-click="AddEstimation($index);"><a>Add</a></span></td>
<td><span ng-click="DeleterowEstimation($index);"><a>Delete</a></span></td>
</tr>
</table>
$scope.moveFocus = function (nextId,prevId,downId,upId,index,event) {
debugger;
if (event.keyCode == 39) {
nextId = nextId + index;
$('#' + nextId).focus();
}
else if(event.keyCode == 37)
{
prevId = prevId + index;
$('#' + prevId).focus();
}
else if(event.keyCode == 38)
{
upId = upId + (index - 1);
$('#' + upId).focus();
}
else if(event.keyCode == 40)
{
downId = downId + (index + 1);
$('#' + downId).focus();
}
else if(event.keyCode==13)
{
if (nextId == "desc") {
nextId = "quantity" + index;
$('#' + nextId).focus();
}
else if(nextId == "uom")
{
nextId = "stkid" + (index + 1);
$('#' + nextId).focus();
}
}
};
On Enter press it moves to next element of DOM, but element requires id to set focus
starter.directive('focustonext', function () {
return {
restrict: 'A',
link: function ($scope, selem, attrs) {
selem.bind('keydown', function (e) {
var code = e.keyCode || e.which;
if (code === 13) {
e.preventDefault();
var pageElems = document.querySelectorAll('input, select, textarea'),
elem = e.srcElement || e.target,
focusNext = false,
len = pageElems.length;
for (var i = 0; i < len; i++) {
var pe = pageElems[i];
if (focusNext) {
if (pe.style.display !== 'none') {
document.getElementById(pe.id).focus();
break;
}
} else if (pe === elem) {
focusNext = true;
}
}
}
});
}
}
});
Thanks all..