Is it possible to chain an event handler programatically in a directive? - javascript

I've been trying to figure out a way to trigger the validation routines on an input element when a button is clicked in the same form. I've been looking at a few different ways of doing this. What seems to be the least convoluted is to create a directive that modifies an input button to fire the $validate method on the target form element. I've set this up without too much trouble but I've gotten blocked at how to modify the ngClick event handler so that it triggers the $validate while leaving the original HTML-defined ngClick intact.
I was attempting to use the directive template function to extract the original ngClick method and chain it to the new ngClick function defined in the directive. This started to turn into a mess quite quickly and I'm concerned about how brittle it might be.
Is there a way to intercept the ngClick handler in a directive and to still have the original functionality intact?
Alternately, I'm open to suggestions about how to fire the validation routines on the input field when the button is clicked with minimal involvement of the controller layer.

This is a classical example of an XY-question (if not a double-XY-question).
You don't need to "chain event handlers" (whatever you mean by that). Neither do you need to, I think, trigger the validation manually just because you are validating against external data.
Validation in Angular just runs - and it is not meant to be triggered other than by changing the data.
To add your own custom validator you need to create a directive (which it seems like you did). In that directive you probably need to specify what you are validating against, like an array of strings against which you want to check for duplicates.
Let's say, for simplicity, that you want to validate against another value in the ViewModel. Suppose, this how it would be used:
<input ng-model="bar">
<form name="form1">
<input ng-model="foo" not-equal-to="bar">
</form>
<span ng-show="form1.$error.notEqualTo">error: foo is equal to bar</span>
So, you need to create a directive notEqualTo that adds a validator to the ngModel.$validators pipeline. This directive also needs to $watch for changes to bar and re-set the validity:
app.directive("notEqualTo", function(){
return {
require: "ngModel",
scope: {
notEqualTo: "="
},
link: function(scope, element, attrs, ngModel){
// register "notEqualTo" validator
ngModel.$validators.notEqualTo = function(modelValue){
return validate(modelValue, scope.notEqualTo);
};
// rerun validation on changes to scope.notEqualTo
scope.$watch("notEqualTo", function(){
ngModel.$setValidity("notEqualTo",
validate(ngModel.$modelValue, scope.notEqualTo));
});
function validate(one, other){
return one !== other;
}
}
};
});
plunker

Related

ng-disabled overrides custom directive behavior

I'm developing the AngularJS client. I've got a custom directive used as an attribute. The directive checks the access level of the element and sets it to be disabled if the current user is not allowed to use it.
The problem begins when the same element has ng-disabled attribute. In this case the ng-disabled sets the ability of the element, never mind of what I set in my custom directive.
For example I have a button that should be disabled in case the form is invalid. At the same time I'd like to use my custom directive in order to set the button to be disabled if the user doesn't have a permition to use it.
<button ng-disabled="myFrm.myCtrl.$invalid" my-custom-directive="controlName"/>
Inside myCustomDirective I check if the named control is allowed to be activated by the user. If not - I set the disabled attribute to the element. But in case myFrm.myCtrl.$invalid is false ng-disabled removes the disabled attribute and the button is enabled.
Is there any solution to this problem? How can I prevent from ng-disabled to perform its operation?
Does your example directive reflect how you applied your actual directive to the button element? I noticed that your example directive is not following the correct naming convention of making each upper case letter in the directive name a lower case letter with a leading hyphen. i.e. myCustomDirective should be applied to the button like so:
<button ng-disabled="myFrm.myCtrl.$invalid" my-custom-directive="controlName"/>
Otherwise your directive will not be complied or linked for that button.
The only other thing I could think of without seeing the actual code is the priority of the directive. According to the angular docs for the ngDisabled directive:
This directive executes at priority level 100.
By default custom directives have a priority of 0. As long as you have not changed this directive parameter your directive should be compiled after ng-disabled and linked after ng-disabled and therefore have the final say on whether or not the button is disabled or enabled.
-- EDIT --
You should add a watch to myFrm.myCtrl.$invalid as was suggested by another and reapply your directives rules whenever the value changes. You don't need to pass the whole controller into your directive though, you could simply use two way binding on myFrm.myCtrl.$invalid. I am not sure how $watch priority works yet. I am hoping that because your directive will be compiled first it will apply its watch on myFrm.myCtrl.$invalid after ngDisabled does and hopefully that means it will handle value changes of myFrm.myCtrl.$invalid after ngDisable does so that your directive has the final say on what rule is applied.
Finaly I've found a solution to my problem. Instead of watching ng-disabled variable I've totaly diconnected the ng-disabled from its variable. In order to do it I've set the ng-disabled to be allways true in my own directive. My directive is an attribute, so after I've tried some different ways to do it, the solution was to add in my directive constructor the following code:
this.isDisabled = "true";
this.attrs["ngDisabl​ed"] = this.isDisabled;
It's very very important to set the this.isDisabled to "true" as a string and not boolean. Otherwise it doesn't work.

AngularJS ngModel Directive with select field and ngOptions

I'm trying to have some code run during the link callback of the ngModel directive using AngularJS on a select field that uses ngOptions.
module.directive("ngModel",function(){
console.log('ng-model called');
return {
restrict: 'A',
priority: -1, // give it lower priority than built-in ng-model
link: function(scope, element, attr) {
console.log('watching');
scope.$watch(attr.ngModel,function(value){
if (value){
console.log("changing");
}
});
}
}
});
See this fiddle which demonstrates the problem:
http://jsfiddle.net/d3r3zwLj/3/
The first select field is populated using ng-options, while the second has its options explicitly written out in html. If you open up the console, you can see that you only see the "changing" message when you change the second select field. Changing the first does nothing.
You'll also notice immediately that you only see 'ng-model called' and 'watching' once, even though there are two fields with ng-model on them.
I'd expect ngModel directive to work on both select fields. What is ng-options doing that is preventing the ngModel from working?
Thanks!
Looking at the doc for ng-options:
https://docs.angularjs.org/api/ng/directive/ngOptions
"Directive Info This directive executes at priority level 0."
If you change the priority of the directive to 0, it runs successfully. I'm not sure what all the implications are there, but that's why it was happening.
When you use the ng-options directive, it has another optional ng-model directive, which actually sets the default value to be shown from the options. So in your case, the second list does not have a ng-options directive which makes ng-model look like a regular one. Maybe you can just change the name of your directive.
This is a bad way to update functionality of built in directive, decorators are there for this purpose. Check the documentation

AngularJS multiple forms - how to submit forms by name?

I'm using Angular UI Bootstrap accordion with a form in each Accordion. How can I submit the form by the id or name of the form? In essence I have an click event bound to the accordion and I would like to pass the name of the form as a parameter somehow and then submit it?
thanks
http://plnkr.co/edit/GMJ8fTWqSw2STnG4V2Ri?p=preview
Instead of passing in the form's name or id, you should pass in the object that the form is bound to, or just dynamically pull it from the controller's $scope.
In angular it is considered bad form to have anything DOM related in your controller. By using the DOM in a controller it dramatically reduces the test-ability of the controller which is one of the key pillars that angular is built upon.
example:
<form ng-submit="handleSubmit(formData)">
<input ng-model="formData.field1"/>
<input ng-model="formData.field2"/>
</form>
Based upon your plnkr example to call the function defined in your form's rcSubmit attribute do this:
formElement.bind('submit', function () {
$parse(attributes.rcSubmit)();
});
The documentation for $parse states Converts Angular expression into a function which is exactly what you need in this case.

JQuery change behaving inconsistently across browsers

I'm new to JavaScript and I'm having a bit of a problem with some JQuery.validate code that I've inherited. Please forgive me if the answer is obvious :)
I have the following validation method declaration:
jQuery.validator.addMethod("myMethod", function(value, element, param) {
/*...*/
if (document.getElementById("DOMID") {
/*...*/
}
/*...*/
)};
Essentially the method is checking for the existence of a certain DOM object and it sets some different validation parameters if it does exist. The object I want to validate is a set of <select> inputs wrapped in a parent <div id="DOMID">, but there are a couple of different "DOMID" cases which each have slightly different validation rules - hence the check inside the method. I want to validate the <select>s each time one of them changes their value. I then have this activation:
$(".myClass").validate({
onchange: true,
});
With myClass being applied to all of the <select> inputs I want to validate.
Lastly, I have this registration:
jQuery.validator.addClassRules(
{
myClass:
{
myMethod: true,
}
}
This works just fine in Chrome - validation is applied each time I change one of the <select> options.
However, in Firefox, this is not the case - I find that I have to blur the select in order for the validation to activate. So, that's one question: Why does change behave differently between Chrome and Firefox? Or is that not the case, and there's something else in my code which is responsible for this inconsistency?
The second question I have is about something I find quite peculiar. In attempting to fix the issue of Firefox not validating until blur, I changed my JavaScript getElementByID... to use jQuery, instead:
if ($("#DOMID").length > 0) { [...]
(I also tried if ($("#DOMID").get(0)) but they do the same thing, I think - unfortunately, neither made Firefox behave properly)
The weird part is that after I made this change, Chrome stopped validating on change and would only validate on blur. That's the only line of code I made a change to - I've since switched it back and Chrome behaves properly again. Why does that happen? How would the internals of the validation method affect when it was being called?
Hopefully this question makes sense - please let me know if any additional information would be helpful! Also any words of wisdom for debugging this problem would be very appreciated. Thanks very much for any advice!
You're using the jQuery Validate plugin improperly...
Quote OP:
"I want to validate the <select>s each time one of them changes their value. I then have this activation:"
$(".myClass").validate({
onchange: true,
});
1) You cannot attach .validate() to any single input element. The .validate() method can only be attached to the <form> element...
$('#myForm').validate({
// options, rules, etc.
});
2) There is no such plugin option called onchange. You are only allowed to use the options that were created by the developer specifically for this plugin.
If you want to add and remove rules based on a select element, then use the .rules('add') and .rules('remove') methods.
$('#myselect').on('change', function() {
$('#myInput').rules('add', { // add rule(s) to #myInput
required: true
});
});
If instead you want to "validate" or test the validity of something, use the .valid() method.
$('#myselect').on('change', function() {
$('#myInput').valid(); // trigger a test of #myInput
});

customizing ngModel directive to support model->view binding in jquery plugin

I am decorating a standard text input with the jquery plugin for tags input, which essentially replaces the text input in my HTML with a text area where users can write tag names and hit enter to have them converted to discrete graphical tags. (see demo at http://xoxco.com/projects/code/tagsinput/)
I am placing an ngModel directive in the original text input.
I am able to keep my scope updated with changes made in the tags input field by listening to the change handler from the plugin, parsing the ngModel attribute from the original text input html tag, and updating the scope directly.
However, the issue is that when Angular detects a change in the model and updates the view using binding, it is setting the value on the original text input which does not cause any sort of event I can bind to to know when to update the plugin's value, since "change" only fires due to user input.
Is there a way to modify the default ngModel directive behavior to have it fire an event or run a function I specify when it is processing bindings, specifically from model to view?
You need to override the ngModel.$render function
directive.tabbable = function() {
return {
require: 'ng-model',
link: function(scope, element, attrs, ngModel) {
// do work to register the jQuery widget
ngModel.$render = function() {
// read ngModel.$viewValue and update the jQuery widget with it.
}
}
};
};

Categories

Resources