I'm just starting to get to grips with angular and I am trying to do something that I think should be pretty simple, but I can't find anyone who has posted with exactly the same scenario. I have a collection which is initiated with three objects and I am using ng-repeat to generate a set of input fields for each of these objects. When the SPA is initialised I want the first input field to have focus: I can do with with autofocus if necessary. When the user TABs off the last input field I add another object to the collection using ng-blur="addRecord($index)". When the DOM is refreshed I want the first field in the new object to have focus. The difference between my effort and all the examples I can find online is that all the examples initiate the addition using a button and an ng-click event.
Because the DOM element is going to be created on the fly, I think I need a custom directive with a $timeout but this seems like a lot of work for what should be a fairly standard requirement. I am using 1.3.x Can anyone show me the basics of how to write the directive or point me at a library that already exists that will do what I want. My current code is set out below.
HTML
<body ng-app="myApp">
<div ng-controller="playerController">
<ul>
<li ng-repeat="player in players">
<input type="text" placeholder="FirstName" ng-model="player.firstName"></input>
<input type="text" placeholder="NicktName" ng-model="player.nickName"></input>
<input type="text" placeholder="SurnameName" ng-model="player.lastName" ng-blur="addNew($index)"></input>
{{player.firstName}} "{{player.nickName}}" {{player.lastName}}
</li>
</ul>
</div>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="myApp.js"></script>
</body>
myApp.js
var myApp = angular.module('myApp',[]);
myApp.controller('playerController',function($scope){
$scope.players = [
{
"firstName":"Aaron",
"lastName":"Reese",
"nickName":"Star Wars",
"givemefocus": "true"
},
{
"firstName":"Ian",
"lastName":"Soinne",
"nickName":"Dominian",
"givemefocus": "false"
},
{
"firstName":"Aaron",
"lastName":"Bailey",
"nickName":"Fernando",
"givemefocus": "false"
}
];
$scope.addNew = function($index){
if($index == (players.length -1 )){
$scope.newPlayer = {
"firstName":"",
"lastName":"",
"nickName":"",
"givemefocus": "true"
};
$scope.players.push($scope.newPlayer);
}
}
});
app.directive('takefocus', function($timeout) {
return function(scope, element, attrs) {
scope.$watch(attrs.takefocus, function(value) {
if (value) {
$timeout(function() { element.focus(); });
}
});
};
});
In html:
<li ng-repeat="player in players">
<input type="text" placeholder="FirstName" ng-model="player.firstName" takefocus="player.givemefocus"></input>
Add Id to first input <input id=input{{$index}}../> to find this input later in onBlur function.
<li ng-repeat="player in players">
<input id="input{{$index}}" type="text" placeholder="FirstName" ng-model="player.firstName"></input>
<input type="text" placeholder="NicktName" ng-model="player.nickName"></input>
<input type="text" placeholder="SurnameName" ng-model="player.lastName" ng-blur="addNew($index)"></input>
{{player.firstName}} "{{player.nickName}}" {{player.lastName}}
</li>
Add $timeout to controller. In function addNew use $timeout with zero delay to wait to the end of DOM rendering. Then input can be found by getElementById in $timeout function.
myApp.controller('playerController',function($scope, $timeout)
{
$scope.addNew = function($index){
if($index == (players.length -1 )){
$scope.newPlayer = {
"firstName":"",
"lastName":"",
"nickName":"",
"givemefocus": "true"
};
$scope.players.push($scope.newPlayer);
$timeout(function ()
{
document.getElementById("input" + ($index + 1)).focus();
});
}
}
});
Related
Here is a simple Angular example:
<!DOCTYPE html>
<html ng-app="GenericFormApp">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
</head>
<body ng-controller="GenericFormCtrl as ctrl">
<div>
Model: {{ctrl.model}}
</div>
<div>
<input ng-model="ctrl.model" />
</div>
<div>
<input type="button" value="Alert model" ng-click="ctrl.showModel();" />
</div>
<script>
angular.module("GenericFormApp", [])
.controller("GenericFormCtrl", [function () {
this.showModel = function () { alert(this.model); };
}])
</script>
</body>
</html>
The above shows how to bind an input to a model, a fundamental feature of Angular.
It also allows the user to pop up a modal dialog with the contents of the input. This works fine except when the input is left blank.
In that case, it displays "undefined".
I could, of course, simply write a line of code that sets the initial value of the model to a blank string, but this is not particularly practical because in my real application, there are many inputs, and the user may leave any number of them blank.
In short, I want to know how to make it so that Angular knows that a blank input should contain a blank string in the model.
I would go with custom directive to extend default input directive behaviour. So in case if input has a model this directive would check if this model is undefined and if so assign it an empty string value.
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
<div ng-app="GenericFormApp" ng-controller="GenericFormCtrl as ctrl">
<input ng-model="ctrl.model" /> {{ctrl.model}}<br>
<input type="button" value="Alert model" ng-click="ctrl.showModel();" />
<script>
angular.module("GenericFormApp", [])
.controller("GenericFormCtrl", [function () {
this.showModel = function () { alert(this.model); };
}])
.directive("input", function($parse) {
return {
link: function(scope, element, attr, ngModelController) {
if (attr.ngModel) {
var model = $parse(attr.ngModel);
if (typeof model(scope) === 'undefined') {
model.assign(scope, '');
}
}
}
};
});
</script>
</div>
I igree with #Claies, but, if you need this for some specific attributes, you can use ng-init:
<input type="text" ng-init="ctrl.model = ctrl.model || ''" ng-model="ctrl.model"/>
or create a specific directive, like 'auto-init' or similar, not directly on input element.
I'm trying to build an Angular JS form. I'd like user to be able to set the focus on a text field when they click a button. Not sure why this doesn't work? Thanks
html:
<div ng-app="" ng-controller="personController">
<p>Name: <input type="text" ng-model="name" id="question1" focus="{{focusThis}}"></p>
<p ng-bind="name"></p>
<input type="button" value="focus" ng-click="focus()">
</div>
Angular JS function:
function personController($scope)
{$scope.focus=function(){
$scope.focusThis="true";
};
};
How about some general solution ($ is jQuery):
mod.directive('nsFocusId', ['$timeout', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.click(function() {
$timeout(function () { $('#' + attrs.nsFocusId).focus(); }, 0);
});
}
};
}]);
Usage:
<label data-ns-focus-id="id-of-target-element">a</label>
This directive could be used on any element and it will focus element by id that you provide.
I've used $timeout just to make it more flexible for future upgrades (but feel free to wrap it with just scope.$apply function);
https://docs.angularjs.org/api/ng/directive/ngFocus
ng-focus executes an expression when the element is focused, so it doesn't actually set the element as focused but rather respond to it being focused.
How to set focus on input field?
Check this resource or google up 'how to set an element focused' and it should direct you in the right way.
I have modified your code, check it.
<div ng-app="TestApp" ng-controller="personController">
<p>Name: <input type="text" ng-model="name" id="question1" ></p>
<p ng-bind="name"></p>
<input type="button" value="focus" ng-click="focus()">
</div>
var app = angular.module('TestApp', []);
app.controller('personController', function ($scope, $http, $log) {
$scope.focus = function () {
$("#question1").focus();
console.log($("#question1"));
}
});
I was developing a todo-like application with jQuery but I'd like to switch to Angular.
There is an input field for adding a new item, but as soon as anything is typed into this input, the key stroke -and those following it -are effectively moved to a new item among the group of existing items. This means that the new item input text box always remains empty. Better to show than explain:
http://jsfiddle.net/29Z3U/4/ (Many details removed, but the aspect of the app to which I'm referring is demonstrated).
<h1>Song List</h1>
<form id="songs">
<ul id="sortable_songs"></ul>
</form>
<ul id="new_song">
<script id="song_form_template" type="text/x-handlebars-template">
<li class="song" id="{{song_id}}">
<input type="text" placeholder="enter song" autofocus />
</li>
</script>
</ul>
Some jQuery:
var template = Handlebars.compile($('#song_form_template').html()),
counter = (function(){var i=0; return function(){return ++i};})(),
cloneNewSong = function(){
var count = counter(),
templateVals = {song_id: 'song_' + count};
$('ul#new_song').append(template(templateVals));
},
addSong = function(event){
//exclude certain keys here
cloneNewSong();
container = $(event.target).closest('li.song');
container.appendTo('ul#sortable_songs');
$(event.target)
.removeAttr('placeholder')
.focus(); //what I just typed disappears without this! why?
};
$('ul#new_song').on('keypress', 'input', addSong);
cloneNewSong();
Note that the new item input text box always remains empty and the focus behaves as it should so you can continue typing without interruption.
The application code is getting lengthy and I've not even yet attempted to display an existing list of songs derived from JSON. Of course in Angular, ngRepeat makes this easy. However, my attempt at an Angular version doesn't work: http://plnkr.co/edit/xsGRiHFzfsVE7qRxgY8d?p=preview
<!DOCTYPE html>
<html ng-app="songListApp">
<head>
<script src="//code.angularjs.org/1.2.7/angular.js"></script>
<link href="style.css" rel="stylesheet" />
<script src="script.js"></script>
</head>
<body ng-controller="songListController">
<h1>Song List</h1>
<ul id="songs">
<li ng-repeat="song in songs">
<input type="text" ng-model="song.song_name" />
</li>
</ul>
<form>
<input
ng-keypress="newSong(new_song)"
ng-model="new_song.title"
placeholder="enter song" autofocus
/>
</form>
</body>
</html>
JS:
var myapp = angular.module('songListApp', []);
myapp.controller('songListController', function($scope){
songs = [
{"song_name":"song 1","more_info":""},
{"song_name":"song 2","more_info":""},
{"song_name":"song 3","more_info":""}
];
$scope.songs = songs;
$scope.newSong = function(new_song){
var song = {"song_name": new_song.title, "more_info":""};
songs.push(song);
new_song.title = '';
};
});
Before even tackling the problem of focus management, I notice that the updating of Angular's model always lags behind by one keystroke. I assume this is because the keypress event happens before the character is inserted into the DOM.
I realise that switching from keypress to keyup would change things but the original design was based on the responsiveness of keypress.
I tried a custom directive to bind to the input event but things don't behave as I'd hoped: http://plnkr.co/edit/yjdbG6HcS3ApMo1T290r?p=preview
I notice that the first code example at angularjs.org (basic binding with no controller) doesn't seem to suffer from the issue I have -the example model is updated before a key is released.
Although there is an accepted answer, I present a different approach, which is a lot simpler with less tainted controller.
First, controller. it's just simple.
myapp.controller('songListController', function($scope, $timeout){
$scope.songs = [
{"song_name":"song 1","more_info":""},
{"song_name":"song 2","more_info":""},
{"song_name":"song 3","more_info":""}
];
});
Second, tag part
<input ng-keypress="createAndFocusIn(songs)"
create-and-focus-in="#songs input"
placeholder="enter song"
autofocus
/>
To explain,
when a key is pressed, it create a song and focus in songs list, createAndFocusIn(songs).
create-and-focus-in directive provides scope.createAndFocusIn(), so that controller does not have to have this function unless this directive is used. It accepts the selector, where to create new element, in this case, #songs input
Last but most importantly, directive part:
myapp.directive('createAndFocusIn', function($timeout){
return function(scope, element, attrs){
scope.createAndFocusIn = function(collection) {
collection.push({song_name: String.fromCharCode(event.keyCode)});
$timeout(function() {
element[0].value = '';
var el = document.querySelectorAll(attrs.createAndFocusIn);
el[el.length-1].focus();
});
};
};
});
in directive, it isn't doing much else except that is specified by attributes.
That's it.
This is working demo: http://plnkr.co/edit/mF15utNE9Kosw9FHwnB2?p=preview
---- EDIT ----
#KnewB said it does not work in FF. Chrome/FF working version here.
In FF,
window.event is not set when key pressed. we need to pass as parameter
event.keyCode does not exist, we need to check both keyCode || charCode
value and focus makes cursor to the first position, so needed to set focus first then add value
http://plnkr.co/edit/u2RtHWyhis9koQfhQEdW?p=preview
myapp.directive('createAndFocusIn', function($timeout){
return function(scope, element, attrs){
scope.createAndFocusIn = function(ev, collection) {
collection.push({});
$timeout(function() {
element[0].value = '';
var el = document.querySelectorAll(attrs.createAndFocusIn);
el[el.length-1].focus();
el[el.length-1].value = String.fromCharCode(ev.keyCode||ev.charCode);
});
};
};
});
and $event now passed:
<input ng-keypress="createAndFocusIn($event, songs)"
create-and-focus-in="#songs input"
placeholder="enter song"
autofocus
/>
solved:) btw. neat component! here is the plunkr: http://plnkr.co/edit/wYsFRUcqTZFv5uIE0MWe?p=preview
the html:
<body ng-controller="songListController">
<h1>Song List</h1>
<ul id="songs">
<li ng-repeat="song in songs">
<input type="text" ng-model="song.song_name" focus-me="song === newSong"/>
</li>
</ul>
<form>
<input
ng-keypress="createNewSong()"
ng-model="newSongTitle"
placeholder="enter song" autofocus
/>
</form>
</body>
I have changed the ng-keypress function, so that a new song is completely created in the controller. Also not the new directive focus-me - if the current song is the new created song, the input field gets the focus.
the controller:
myapp.controller('songListController', function($scope, $timeout){
$scope.songs = [
{"song_name":"song 1","more_info":""},
{"song_name":"song 2","more_info":""},
{"song_name":"song 3","more_info":""}
];
$scope.newSongTitle = '';
$scope.newSong = null;
$scope.createNewSong = function(){
$timeout(function(){
$scope.newSong = {"song_name": $scope.newSongTitle, "more_info":""};
$scope.songs.push($scope.newSong);
$scope.newSongTitle = '';
});
};
});
As you can see the creation of the new song is wrapped by a $timeout call. This call delayed the execution until the next digest cycle happens, so no pending events can interrupt us.
finally the directive:
myapp.directive('focusMe', function(){
return function($scope, element, attr){
if($scope.$eval(attr.focusMe)){
element[0].focus();
}
};
});
for sure generic, so that every expression can trigger the focus.
I want to render a form, based on a dynamic field configuration:
$scope.fields = [
{ title: 'Label 1', type: 'text', value: 'value1'},
{ title: 'Label 2', type: 'textarea', value: 'value2'}
];
This should output something that behaves like:
<div>
<label>{{field.title}}<br />
<input type="text" ng-model="field.value"/>
</label>
</div>
<div>
<label>{{field.title}}<br />
<textarea ng-model="field.value" rows="5" cols="50"></textarea>
</label>
</div>
The simple implementation would be to use if statements to render the templates for each field type. However, as Angular doesn't support if statements, I'm lead to the direction of directives. My problem is understanding how the data binding works. The documentation for directives is a bit dense and theoretical.
I've mocked up a bare bones example of what I try to do here: http://jsfiddle.net/gunnarlium/aj8G3/4/
The problem is that the form fields aren't bound to the model, so the $scope.fields in submit() isn't updated. I suspect the content of my directive function is quite wrong ... :)
Going forward, I need to also support other field types, like radio buttons, check boxes, selects, etc.
The first problem you are running into regardless of the directive you are trying to create is using ng-repeat within a form with form elements. It can be tricky do to how ng-repeat creates a new scope.
This directive creates new scope.
I recommend also instead of using element.html that you use ngSwitch instead in a partial template.
<div class="form-row" data-ng-switch on="field.type">
<div data-ng-switch-when="text">
{{ field.title }}: <input type="text" data-ng-model="field.value" />
</div>
<div data-ng-switch-when="textarea">
{{ field.title }}: <textarea data-ng-model="field.value"></textarea>
</div>
</div>
This still leaves you with the problem of modifying form elements in child scope due to ng-repeat and for that I suggest using the ngChange method on each element to set the value when an item has changed. This is one of the few items that I don't think AngularJS handles very well at this time.
You might consider Metawidget for this. It uses JSON schema, but is otherwise very close to your use case. Complete sample:
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js" type="text/javascript"></script>
<script src="http://metawidget.org/js/3.5/metawidget-angular.min.js" type="text/javascript"></script>
<script type="text/javascript">
angular.module( 'myApp', [ 'metawidget' ] )
.controller( 'myController', function( $scope ) {
$scope.metawidgetConfig = {
inspector: function() {
return {
properties: {
label1: {
type: 'string'
},
label2: {
type: 'string',
large: true
}
}
}
}
}
$scope.saveTo = {
label1: 'value1',
label2: 'value2'
}
$scope.save = function() {
console.log( $scope.saveTo );
}
} );
</script>
</head>
<body ng-controller="myController">
<metawidget ng-model="saveTo" config="metawidgetConfig">
</metawidget>
<button ng-click="save()">Save</button>
</body>
</html>
The type attribute can be changed when the element is out of DOM, so why not a small directive which removes it from DOM, changes it type and then add back to the same place?
The $watch is optional, as the objective can be change it dynamically once and not keep changing it.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.rangeType = 'range';
$scope.newType = 'date'
});
app.directive('dynamicInput', function(){
return {
restrict: "A",
link: linkFunction
};
function linkFunction($scope, $element, $attrs){
if($attrs.watch){
$scope.$watch(function(){ return $attrs.dynamicInput; }, function(newValue){
changeType(newValue);
})
}
else
changeType($attrs.dynamicInput);
function changeType(type){
var prev = $element[0].previousSibling;
var parent = $element.parent();
$element.remove().attr('type', type);
if(prev)
angular.element(prev).after($element);
else
parent.append($element);
}
}
});
span {
font-size: .7em;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="MainCtrl">
<h2>Watching Type Change</h2>
Enter Type: <input ng-model="newType" /><br/>
Using Type (with siblings): <span>Before</span><input dynamic-input="{{newType}}" watch="true" /><span>After</span><Br>
Using Type (without siblings): <div><input dynamic-input="{{newType}}" watch="true" /></div>
<br/><br/><br/>
<h2>Without Watch</h3>
Checkbox: <input dynamic-input="checkbox" /><br />
Password: <input dynamic-input="{{ 'password' }}" value="password"/><br />
Radio: <input dynamic-input="radio" /><br/>
Range: <input dynamic-input="{{ rangeType }}" />
</div>
Tested in latest Chrome and IE11.
I'm trying to build a simple calculator in Angular in which I can override the total if I want. I have this part working but when I then go back to enter in a number in fields one or two the total isn't updated in the field.
Here is my jsfiddle http://jsfiddle.net/YUza7/2/
The form
<div ng-app>
<h2>Calculate</h2>
<div ng-controller="TodoCtrl">
<form>
<li>Number 1: <input type="text" ng-model="one">
<li>Number 2: <input type="text" ng-model="two">
<li>Total <input type="text" value="{{total()}}">
{{total()}}
</form>
</div>
</div>
The javascript
function TodoCtrl($scope) {
$scope.total = function(){
return $scope.one * $scope.two;
};
}
You can add ng-change directive to input fields. Have a look at the docs example.
I'm guessing that when you enter a value into the totals field that value expression somehow gets overwritten.
However, you can take an alternative approach: Create a field for the total value and when either one or two changes update that field.
<li>Total <input type="text" ng-model="total">{{total}}</li>
And change the javascript:
function TodoCtrl($scope) {
$scope.$watch('one * two', function (value) {
$scope.total = value;
});
}
Example fiddle here.
I wrote a directive you can use to bind an ng-model to any expression you want. Whenever the expression changes the model is set to the new value.
module.directive('boundModel', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var boundModel$watcher = scope.$watch(attrs.boundModel, function(newValue, oldValue) {
if(newValue != oldValue) {
ngModel.$setViewValue(newValue);
ngModel.$render();
}
});
// When $destroy is fired stop watching the change.
// If you don't, and you come back on your state
// you'll have two watcher watching the same properties
scope.$on('$destroy', function() {
boundModel$watcher();
});
}
});
You can use it in your templates like this:
<li>Total<input type="text" ng-model="total" bound-model="one * two"></li>
You just need to correct the format of your html
<form>
<li>Number 1: <input type="text" ng-model="one"/> </li>
<li>Number 2: <input type="text" ng-model="two"/> </li>
<li>Total <input type="text" value="{{total()}}"/> </li>
{{total()}}
</form>
http://jsfiddle.net/YUza7/105/
Create a directive and put a watch on it.
app.directive("myApp", function(){
link:function(scope){
function:getTotal(){
..do your maths here
}
scope.$watch('one', getTotals());
scope.$watch('two', getTotals());
}
})