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

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.
}
}
};
};

Related

Binding a custom JavaScript control to AngularJS

I have a set of pure JavaScript controls that are initiated like this:
var myControl = new SVGColorPicker("name", "#rrggbb");
myControl.insert("tagIdValue");
or, alternatively, in html:
<div><script>myControl.draw()</script></div>
The controls have getValue() and setValue(value) methods, and they harbor a hidden html input element so that the values appear in the form submits.
I am looking for some guidance how to bind these controls to the AngularJS famework. Obviously, I'd create a custom directive for every type. But how to tie in the control value reading and updating so that the two-way binding works flawlessly, and I could also make use of the special AngularJS form functionality (states, validation, etc)

Trigger event on Textinput or Textarea value change MeteorJS

I want add the contents of a textarea (or a textfield) to a reactive variable and bind it to a view, similar to AngularJS double binding.
The problem to listen to keydown is that the last character will not be included. If I use keyup there will be a delay. Is there a way to listen to value change in a text field and immediately after it changes set the reactive variable?
Template.body.events({
"keydown #textarea":function(){
input.set( $('#textarea').val());
}
});
You can use the HTML5 input event which is probably better suited for what you need.
Template.body.events({
"input #textarea":function(){
input.set( $('#textarea').val());
}
});

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

Why ng-model is required for ng-options directive

Markup
<div ng-app>
<div ng-controller="myCtrl">
<select ng-options="i for i in [1,2,3]"></select>
</div>
</div>
With this markup, options are not populated in select. When i provide ng-model along with ng-options it works as expected.
Why ng-model is required for the ng-options to populate the options?
ngOptions needs model in order to determine which option should be selected by default or which model to write selected object/value when selection changes.
If there was no ngModel directive, it would be difficult to access selected value, and it potentially lead to bad-practice code when people either query DOM to determine the value or write their own directives for this purpose. In both cases this is not ideal as this type of things is the key idea of Angular form elements directives.
Also don't forget that ngOptions in conjunction with ngModel allow developer to bind entire objects as the "value" of select elements, which would be tricky without ngModel.
Finally, if for some reason one doesn't want to have any model on select (although it makes little sense), it's possible to render options with ngRepeat.
If there is no model bound to the select, after choosing an option where would the selection be written out to?
As for the reason why the options are not displayed you can see the source code:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngOptions.js#L355
return {
restrict: 'A',
terminal: true,
require: ['select', '?ngModel'],
link: function(scope, selectElement, attr, ctrls) {
// if ngModel is not defined, we don't need to do anything
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
The link function short cuts out and all the render functions are not created.
From the docs:
ngOptions should be used when the model needs to be bound to a non-string value
The very point of ngOptions is to provide values for the model, not to merely render option elements. Therefore it doesn't make sense to be used without ngModel.

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

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

Categories

Resources