Adding Scope to Directive kills model relation - angularjs

The Goal: To provide a series of text fields where when you have it selected and press the enter key it will either add a new text field (if you press enter in the last field) or focus on the next field (if you press enter on any other field).
The Idea: Using a directive that detects enter key presses will trigger a callback to the original controller that will be able to perform the necessary tasks. Ideally this directive will be able to use elsewhere for other actions other than the next / new text fields goal stated above.
The Problem: I've written the directive that will detect the enter key press and will then make a call to the callback function WITH the event data (used to detect which field was active when pressing the enter key). This works great! However... when the scope: [method: '&ngEnter'] is on the directive it kills all relationship between the text field and the controller (??). Therefore all textfields are blank (yet the other sub data found elsewhere (anywhere but the input) function properly, so the data itself is intact and correct, but when applied to the textfield it acts all screwy.
The Code: Found Below...
Directive: ng_enter.coffee
angular.module('app.common').directive "ngEnter", ['$parse', ($parse) ->
restrict: 'A'
scope:
method: '&ngEnter'
link: (scope, element, attrs) ->
expressionHandler = scope.method()
element.bind "keydown keypress", (event) ->
if event.which is 13
event.preventDefault()
scope.$apply ->
expressionHandler(event)
]
my_view.haml
.item-list.sub-item-list.bordered-background
.item-row(ng-repeat='sub in categoryEditor.categorizations' ng-hide='sub._destroy')
%div
%input.left{type: 'text', "ng-model"=>"sub.name", style: 'width: 85%; margin: 0 0 0 1px;', 'ng-enter' => 'subitemEnter'}
%i.icon-large.right.icon-remove{"ng-click"=>"removeSubItem(sub)", title: "{{sub.name}}"}
my_controller.coffee
angular.module('app.admin.controllers').controller 'MyControllerCtrl', ['$scope', ($scope) ->
$scope.categoryEditor = {...}
$scope.subitemEnter = (event) ->
console.log "subitemEnter() Triggered"
console.log event
Update: It appears that scope is being overwritten, and i must find a way to solve this without explicitly declaring scope in my directive.

Installing Angular UI Utils, I was able to get access to ui-keypress that provides a easy way of performing callbacks on various keypress events. The following code is the solution.
View (.haml):
%input{type: 'text', "ng-model"=>"sub.name", 'ui-keypress' => "{13:'subItemEnterPress($event)'}"}
Controller (.coffee):
$scope.subItemEnterPress = (event) ->
event.preventDefault()
inputs = $(event.target).closest('.item-list').find('input')
if event.target == _.last(inputs)
$scope.addSubItem()
else
$(inputs[ _.indexOf(inputs, event.target) + 1 ]).focus()

Related

How can I prevent ng-model from being changed in select based on result of function?

I have a select drop down menu populated with ng-options. I want to create a pop-up that will require the user to confirm they want to change the selected option before continuing.
So far, I have tried using an ng-change, but that doesn't work because you can't prevent the change (for the purposes of my app, I cannot simply put the value back to what it was before the change in an ng-change function). I have tried ng-click, but this fires when the user clicks on the select box, whereas I want this to fire when the user actually selects one of the options from the select box for UI purposes. I have investigated ng-model-options, but my problem is I cannot seem to get an event to fire when an option is selected.
Code below demonstrating what this select looks like:
<select ng-model='uiConfig.selectedInteractionType' ng-options='interactionType for interactionType in uiConfig.interactionTypes'></select>
I had this exact same requirement, albeit for a different element. I had a button that was really an anchor tag where I passed in some values, including a URL when the user clicked the button. I then needed to open an $mddialog modal and let the user cancel or click continue to send them to a redirected site.
What you need is a directive here. Put the value(s) in $scope. I used attributes for this:
restrict: 'A',
and then in my link function I used
attributes.$observe
to pull in my values.
Then use something like this in your directive function:
element.on('click' function ($event) {
$event.preventDefault(); //To stop the event flow.
$mdDialog.show({
templateUrl: "path to your modal html document",
controller: myController,
clickOutsideToClose: true,
locals:{
href: $scope.href,
myValue: $scope.myValue
}
Using $locals allows you to pass in the values to the directive.

Keydown doesn't properly refresh ng-model in input text but keyup does

I need to retrieve in a directive the value of a text input using ng-model to send value to a data base.
If I use in my directive the keydown event, and wrote for exemple 1234 in my input, the result show in my directive is 123, if I wrote abc, the result is ab.
If I use keyup, this problem doesn't exist (tested with firefox and chrome).
Why does it occur?
My code:
.directive('updateDbb', ["$http", function ($http) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
// Listen for change events to enable binding
var ngModelValue, inputName, testValid, dbbFieldName, idDbb;
element.bind('keydown', function () {
ngModelValue = ngModel.$viewValue;
console.log(ngModelValue); // Show with delay, not the case with "keyup"!!!
...
keydown fires when the user presses on a key, and before the character is being inserted into the input. That's why it doesn't get updated.
keypress fires when an actual character is being inserted in your input. I think it is the event you want to use (if the user maintains the key down, the event will be triggered once for per character inserted)
keyup fires when the user releases a key, after the character has been inserted into the input. (if the user maintains the key down, the event will only be triggered once)

focus on last input ng-repeat

I have been looking at a lot of focus directives, especially those posted here.
But none seem to work for my scenario. I have a tabset and a table inside with an ng-repeat that creates rows with inputs on it. The way I create them is using ng-blur at the end of the last row checkbox so the ng-blur fires the addNewRow method. What I want is to focus the first input on the newly created row but the focus goes to the browser search bar instead.
I created a fiddle to show the problem using a directive from the first posted link.
I tested this directive:
soccerApp.directive('setfocus', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var focus = !! attrs.setfocus && !attrs.setfocus.replace(/true/, '');
if (focus === true) {
//alert(element);
element[0].focus();
}
}
};
});
and the element[0].focus(); actually fires when I create a new row but doesn't set the focus.
Can someone please take a look and tell me what could be wrong?
NOTE: The idea is to use the TAB key to loop through the inputs until the last one.
As Deblaton Jean-Philippe pointed out
The problem is that, at the moment you "tab" from the last input, your browser sets the focus out of the page. So you don't have any control on it anymore. I don't know if it's possible to override that without creating a fake invisible input below to keep the focus into the page
i was missing a fake input so i added a fake at the bottom after my table and it worked

How do you set the focus to a control in an angular app?

The question stems from an ng-grid interaction, but I've stubbed my toe on it a few other places.
An interaction with the page raises the need for the focus to be on a certain control.
For ng-grid this might be clicking on the filter button in the header. This causes a popup (but not really a modal dialog) input control to appear which then needs the focus, so the user doesn't then have to click a second time to enter the filter text. I'm assuming this should be done in the directive, but how do you get that directive at the point this is happening?
Another interaction might be after an attempted save on a form. Let say there is a validation that can't happen in the client, (multi-user app, race condition to acquire a resource). When the error returns from the promise, I'd like to put the cursor in the field that needs to be changed. )Which field depends on the error response from the server.)
I think what I'm really looking for is the equivalent of an $('#id').focus() that accepts an ng-model and finds the correct control on the page and puts the cursor in that field, but one that could be used at the completion of promise or in reaction to an event. I realize that linkage from model => DOM is problematic (a model could appear many places on the page), but in the case of form input, that probably isn't true, or could be made not-true to facilitate this kind of application response.
I'm a little lost as to where the logic should be and how I could get hold of the object that would contain it.
There's a similar question that has some good answers. I think the second answer comes closest to what you're asking for
How to set focus on input field?
Instead of binding a boolean "should be focused" field, the directive uses events to set focus on the desired element.
There are several advantages to this approach over the $('#id').focus() functionality you were describing. The biggest one is that with the event approach, your controller can be mocked and tested outside the DOM, because the tests can just look for fired events, rather than checking which DOM element has focus.
Here's a really generic example of how you might do it for field validation. Kudos to #blesh for the directive/service implementations
In your form you can add the focus-on directive. Just use a unique event name for each field:
<input type="text" name="name" focus-on="input-name" />
<input type="text" name="email" focus-on="input-email" />
In your controller, after you do field validation, you can call the "focus" service on the using the focus-on="input-{name}" value you specified in your template. Here I just prepend input- to the field name, but you could use any naming convention you like.
app.controller('MyCtrl', function($scope, focus) {
$scope.validate = function() {
// ...
// Do your field validation
// ...
if (invalidFields.length) {
focus('input-' + invalidFields[0].name);
}
};
});
Then just define your directive and service
app.directive('focusOn', function() {
return function(scope, elem, attr) {
scope.$on('focusOn', function(e, name) {
if(name === attr.focusOn) {
elem[0].focus();
}
});
};
});
app.factory('focus', function ($rootScope, $timeout) {
return function(name) {
$timeout(function (){
$rootScope.$broadcast('focusOn', name);
});
}
});

AngularJS $watch is called on every keypress in a text input

I have a webpage where i am building a form from dynamic json data received from server. I have developed it with KnockoutJS, a while ago. After building the form, i also have a requirement to update the form by doing an ajax request from the server, everytime a value in the form has been changed. Here is the fiddle
You see that, change event only occurs on blur (not on keypress). For example, if a text field has a value 15, and user presses backspace and then enters 5 again, that means 2 keypresses but value has not been changed. So far so good.
Problem 1
Now i am converting the code to AngularJS. I am trying to catch the change event by $watch of scope. But, it seems like watch is run everytime a keypress is happened, even though after few keypresses value is not changed. Here is the fiddle of how i am attempting. This problem is however with only text type input fields.
Problem 2
I am creating radiogroup in a manual way, e.g. if the json data is changed then we will also have to update the html as well. How can i do that in a kind of dynamic way. I was able to do that with KnockoutJS.
Problem 3
Why ng-hide is not working?
Fix for the problem 1 was to introduce a directive for blur event, following is the code of my directive:
angular.module('app', []).directive('ngModelOnblur', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attr, ngModelCtrl) {
if (attr.type === 'radio' || attr.type === 'checkbox') return;
elm.unbind('input').unbind('keydown').unbind('change');
elm.bind('blur', function () {
scope.$apply(function () {
ngModelCtrl.$setViewValue(elm.val());
});
});
}
};
});
you can see the working fiddle here http://jsfiddle.net/akeelrehman/GNJtn/1/
I couldn't figure out the solution for problem 2, so i have stopped using radio inputs.
Instead of using blur, you could just use a timeout. In effect this calls your function when the user has stopped typing for X milliseconds, instead of on every keypress.
See this reply to a similar question, which has example code that does the job: https://stackoverflow.com/a/15723514/1034002

Resources