How to use FormController with directives - angularjs

I'm trying to build widgets directives that play well with forms
The complete example is available at http://jsfiddle.net/jy81bchd/4/
The main idea is:
Write your own form, put widget in it
<form name="otherForm" novalidate ng-init="model = {name: 'test'}" novalidate>
<swif-widget-text name="name" required="required" input-model="model.name">
<span translate>Name</span>
</swif-widget-text>
</form>
The widget directive copy all attributes to the input and transculde the contennt in the the label.
My differents problems are:
1) I can't update the formController of the main form, it doesn't find the different inputs throw directives. The best should be to use formcontrolller.addControl but I don't have succeed with it
2) To work around 1 I have tried to make each widget a different form. This is working execpt formcontroller doesn t update after link has been called (attributed copied to input doesn't affect the controller).
In the fiddle I copied required attribute to the input but if you empty the field it's still valid according to the formcontroller.
I have added name="input" also because if I copy name attribute to the input the form controller doesn't find any input.
Conclusion:
From what I understand formcontroller is initialized and loaded before the link is called.
How could I change that ?

There are several posts about how to create your own form component that plays nicely with the form and the controller that is attached to that.
For instance: http://blog.revolunet.com/blog/2013/11/28/create-resusable-angularjs-input-component/
Examining your fiddle I have a few things you need to look at:
1) add a name and a controller to the form element in order to work with all form elements that live inside of it. Via the $scope that is injected in that controller you can handle the form elements (also add meaningfull names to your form elements).
2) And your custom component:
- should use ng-model instead of input-model.
- should require 'ngModel' in the Directive definition
- in the link phase you get a reference to the ngModelController, and check the link above to see how you should interact with that to achieve a two way binding like behaviour. (using $viewValue, $render and $setViewValue)

Related

Does the <form name="abc"> add an abc object to a $scope?

I have code that adds a form and gives it a name. This is the only way I have been able to get the validation working.
However when I look at the scope I don't seem to be able to see any reference to the form name. Can someone explain how this is added.
Does the <form .. element somehow dynamically create a form object or do I have to create an empty object first?
As explained by the documentation for the form directive:
If the name attribute is specified, the form controller is published onto the current scope under this name.
Therefore, you have access to a FormController object in your scope. That's why you can use it like any other variable, when you do something like that, for instance:
<input
type="submit"
value="Submit !"
ngDisabled="formName.$invalid"
/>

What is the difference between ng-click and data-ng-click in angularjs?

From 1st view seems like data-ng-click can pass some data as argument to method should be invoked during pressing on button.
But I don't see the difference.
I have followed snippets of code:
HTML
<input
type="button"
value="Fess"
ng-click="toggle(2)">
OR
<input
type="button"
value="Fess"
data-ng-click="toggle(2)">
JS
$scope.toggle = function (valueS) {
alert(valueS);
}
Both work.
Thanks,
They are the same thing. You can use data-ng-click to make a valid html.
From the angular docs on directives:
Directives have camel cased names such as ngBind. The directive can be
invoked by translating the camel case name into snake case with these
special characters :, -, or _. Optionally the directive can be
prefixed with x-, or data- to make it HTML validator compliant. Here
is a list of some of the possible directive names: ng:bind, ng-bind,
ng_bind, x-ng-bind and data-ng-bind.
Leaving them out is totally fine for practical purposes. It's just that if you run it through an html validator service, it will not pass as complliant.
HTML5 has an ability to embed custom data attributes on all HTML elements
These new custom data attributes consist of two parts:
Attribute Name
The data attribute name must be at least one character long and must be prefixed with 'data-'. It should not contain any uppercase letters.
Attribute Value
The attribute value can be any string.
<li data-spacing="30cm" data-sowing-time="February to March">Celery</li>
source : http://html5doctor.com/html5-custom-data-attributes/
Found a difference while using with and without Form.
When my element is in a Form they act the same.
When I use data-ng-click on an element not within a form, Click event is not happening.

Dynamic data-binding in AngularJS

I'm building an AngularJS app and I have ran into an issue. I have been playing with the framework for a while and I have yet to see documentation for something like this or any examples. I'm not sure which path to go down, Directive, Module, or something that I haven't heard of yet...
Problem:
Basically my app allows the user to add objects, we will say spans for this example, that have certain attribute's that are editable: height and an associated label. Rather than every span have its own dedicated input fields for height and label manipulation I would like to use one set of input fields that are able to control all iterations of our span object.
So my approx. working code is something like this:
<span ng-repeat="widget in chart.object">
<label>{{widget.label}}</label>
<span id="obj-js" class="obj" style="height:{{widget.amt}}px"></span>
</span>
<button ng-click="addObject()" class="add">ADD</button>
<input type="text" class="builder-input" ng-model="chart.object[0]['label']"/>
<input type="range" class="slider" ng-model="chart.object[0]['amt']"/>
The above code will let users add new objects, but the UI is obviously hardcoded to the first object in the array.
Desired Functionality:
When a user clicks on an object it updates the value of the input's ng-model to bind to the object clicked. So if "object_2" is clicked the input's ng-model updates to sync with the object_2's value. If the user clicks on "object_4" it updates the input's ng-model, you get the idea. Smart UI, essentially.
I've thought about writing a directive attribute called "sync" that could push the ng-model status to the bound UI. I've though about completely creating a new tag called <object> and construct these in the controller. And I've thought about using ng-click="someFn()" that updates the input fields. All of these are 'possibilities' that have their own pros and cons, but I thought before I either spin out on something or go down the wrong road I would ask the community.
Has anyone done this before (if so, examples)? If not, what would be the cleanest, AngularJS way to perform this? Cheers.
I don't think you need to use a custom directive specifically for this situation - although that may be helpful in your app once your controls are more involved.
Take as look at this possible solution, with a bit of formatting added:
http://jsfiddle.net/tLfYt/
I think the simplest way to solve this requires:
- Store 'selected' index in scope
- Bind ng-click to each repeated span, and use this to update the index.
From there, you can do exactly as you proposed: update the model on your inputs. This way of declarative thinking is something I love about Angular - your application can flow the way you would logically think about the problem.
In your controller:
$scope.selectedObjectIndex = null;
$scope.selectObject = function($index) {
$scope.selectedObjectIndex = $index;
}
In your ng-repeat:
<span ng-repeat="widget in chart.object" ng-click="selectObject($index)">
Your inputs:
<input type="text" class="builder-input" ng-model="chart.object[selectedObjectIndex]['label']"/>
<input type="range" class="slider" ng-model="chart.object[selectedObjectIndex]['amt']"/>

How to get the NgModelController for input fields inside a ngRepeat directive?

Normally, with a form and input fields, the form controller is published into the related scope under the form name attribute. And, the NgModelController is published under the input name attribute.
So for an input field with an ngModel directive, the NgModelController for the input field can be retrieved like $scope.myFormName.myInputFieldName
The question is how to do the same thing (get the NgModelController) for input fields inside the ngRepeat directive?
I would like to name the input fields using $index as part of the name so each template instance is uniquely named. This renders OK, so
<input name="foo_{{$index}}" ...
renders the instance with $index == 3 to
<input name="foo_3" ...
But trying to get the ngModelController via the published names does not work (it's undefined), e.g.:
$scope.myFormName.foo_3
A plunker showing this is here: http://plnkr.co/edit/jYDhZfgC3Ud0fXUuP7To?p=preview
It shows successfully getting the ngModelController for a 'plain' input element and calling $setValidity, and also shows failing to get the ngModelController for an input element inside an ngRepeat directive.
Copied the relevant section of code from the plunker below:
<div ng-repeat="element in elements">
<div ng-class="{error: form['foo_{{$index}}'].$invalid}">
<input name="foo_{{$index}}" ng-model="element.a" type="number">
<span ng-show="form['foo_{{$index}}'].$error.bar">ngRepeat bar invalid</span>
</div>
</div>
{{form.foo_0.$setValidity('bar', false)}}
#Flek is correct that the new child scopes that ng-repeat creates are the root of the problem here. Since browsers do not allow nesting of <form> elements, ngForm must be used when nesting forms, or when you want to do form validation inside ngRepeat.
See Pawel's answer on the google group thread, which shows how to use ng-form to create inner forms, and/or #blesh's SO answer.
If i understand your question correctly, you are trying to have access to the form elements created inside the ng-repeat.
Please have a look at this fiddle http://jsfiddle.net/EF5Jp/. Inside the button click handler you will have the access to the element with id myForm.foo_2. You can notice that the element is retrieved by myForm.foo_2 and not $scope.myForm.foo_2. Second thing is, changing the value using its scope and not using its value property like angular.element(element).scope().foo = 6;.

How do I structure AngularJS directive for compound form fields?

I would like to use AngularJS for some administration forms. It's very easy to bind view and model. I also like how you can add validation. But when I tried to prevent redundant code by introducing directives I hit a border. Could you please help me how I should structure code with AngularJS for this problem:
In my forms I have some compound fields. For example you can choose a country in a dropdown. Next to that dropdown you can enter a city in a textfield. When you choose another country the city name should be cleared. That's easy. Now I want autocompletion in the city textfield. When you enter a few characters you get suggestions for cities in the selected country. The autocompletion widget is the minor problem here, let's for simplicity say I would use JQueryUI for that.
I already managed to implement simple directives. My questions are:
1.) I like the functionality for validation in NgModelController. Can I somehow reuse this in my directive? It would be nice if I could add a "required" attribute to my directive. If it is set then all compound fields are required.
2.) How would I connect the directive with the surrounding model? For example I would like to edit a customer in an administration form and my "address" directive should display and edit the customer address.
3.) How would I connect the directive with my city lookup service? I need to give country and first typed letters of city to my service. Result is a list of city names that can be displayed in the autocompletion widget.
Here is the plunker that show you how to set require attribute.
Main idea is having required="{{isRequired}}" in the template and having isRequired as attribute for your directive.
template: '<input name="city" type="text" ng-model="city" placeholder="City" required="{{isRequired}}">'

Resources