AngularJs empty form and remove invalid state from inputs - javascript

I'm using a form to add elements to list that is displayed on the side of the form.
Markup is:
<form name="thingForm">
<input required type="text" ng-model="thing.name"/>
<input required type="text" ng-model="thing.value"/>
<input type="submit" ng-click="addThing(thing)"/>
</form>
<ul>
<li ng-repeat="thing in things">{{thing.name}} with value of {{thing.value}}</li>
</ul>
And in a controller I have:
$scope.things = [];
$scope.addThing = function(thing) {
$scope.things.push(thing);
$scope.thing = {};
};
Working jsfiddle: http://jsfiddle.net/cXU2H/1/
Now as you can see, I can empty the form by emptying the model, however since the inputs have the required tag the browser still displays an error message (at least Chrome does).
I looked at the similar questions and:
I've also looked at this answer: https://stackoverflow.com/a/16296941/545925 however the jsfiddle behaves exactly the same as in my example: after the input is cleared it still has an ng-invalid-required class remaining (and it also triggers a HTML5 error message)
since I'm not on the 1.1.x branch $setPristine() is not available for me $setPristine() behaves the same way
I can of course write a function that iterates through the elements of a form and removes every ng-invalid-required and ng-invalid class, but that is not the way I would like to solve this. That is what I would do with jQuery.

Are you using $setPristine right? You can easily see in your fiddle that if you add it, it works. http://jsfiddle.net/X6brs/
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.things = [];
$scope.addThing = function(thing) {
$scope.things.push(thing);
$scope.thing = {};
$scope.thingForm.$setPristine(true);
};
}

$scope.thingForm.$setPristine();
$scope.thingForm.$setUntouched();
will do the trick.

Related

nickperkinslondon angular treeview expand_all() problems

I'm using this angular treeview project:
https://github.com/nickperkinslondon/angular-bootstrap-nav-tree
I think that this treeview haven't got functions to do searches over treeview, so I implemented mine using a form to write the label to find.
<form name="searchForm" novalidate style="margin-bottom: 50px">
<div> <input ng-model="search" type="text" class="form-control" placeholder="Buscar..." name="searchTerm" required />
</div>
</form>
This form has a .watch to detect when the user writes some text:
$scope.$watch('search', function(newTerm, oldTerm) {
filteredResponse = !newTerm ? response : updateFilteredResponse(normalize(newTerm));
updateTree();
}, true);
The function 'updateFilteredResponse' filter the nodes with label containing newTerm over original data set (read from json) and returns an array with the items to show in treeview.
The 'updateTree' function use this array and transform my custom items in the array to treeview items to add to the treeview. This items are added to
$scope.tree_data = [];
And this array is the one that uses abn-tree directive:
<abn-tree tree-data="tree_data" tree-control="my_tree" ng-if="loaded" expand-level = "2"></abn-tree>
This part is working fine. My problem comes when the result treeview is shown at screen, the treeview always appears completely collapsed.
If I put a button similar to the library example code like this:
<div style="vertical-align:top">
<button ng-click="my_tree.expand_all()" class="btn btn-default btn-sm">Expand All</button>
</div>
And declaring this in the controller as the example:
var tree;
$scope.my_tree = tree = {};
When the users click the button to expand all over the search results, it works fine. By I need to auto-expand the treeview after a search, and remove the expand-all-button.
For that, I'm trying to call my_tree.expand_all() in my controller. I tried different calls:
$scope.my_tree.expand_all();
tree.expand_all();
In different parts of my controller and my html (using ngIf and onload directives). Even I tried to do a 'watch' over $scope.my_tree for try to use the expand_all() function when var is prepared but I always have the same error:
$scope.my_tree.expand_all is not a function
Can anyone help me with that please?
You can put expand_all() function into setTimeout function as below.
setTimeout(function() {
$scope.my_tree.expand_all();
$scope.$digest();
}, 0);
It have to do because it take some delay time while binding data to treeview.

What causes AngularJS variable change to fire html update?

I got this example from the W3Schools tutorial on AngularJS. I made a small change from binding the value of the checkbox span to using an expression. I figured that the todo list wouldn't update any more. But it still does. What causes the ng-repeat to fire just because I have added a todo item?
http://plnkr.co/edit/Kojz2ODWDS8dFDNzjYR5?p=preview
<!DOCTYPE html>
<html>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<body ng-app="myApp" ng-controller="todoCtrl">
<h2>My Todo List</h2>
<form ng-submit="todoAdd()">
<input type="text" ng-model="todoInput" size="50" placeholder="Add New">
<input type="submit" value="Add New">
</form>
<br>
<div ng-repeat="x in todoList">
<input type="checkbox" ng-model="x.done"> <span>{{x.todoText}}</span>
</div>
<p><button ng-click="remove()">Remove marked</button></p>
<script>
var app = angular.module('myApp', []);
app.controller('todoCtrl', function($scope) {
$scope.todoList = [{todoText:'Clean House', done:false}];
$scope.todoAdd = function() {
$scope.todoList.push({todoText:$scope.todoInput, done:false});
$scope.todoInput = "";
};
$scope.remove = function() {
var oldList = $scope.todoList;
$scope.todoList = [];
angular.forEach(oldList, function(x) {
if (!x.done) $scope.todoList.push(x);
});
};
});
</script>
</body>
</html>
Clicking the Add New Button submits the corresponding form and by using ng-submit="todoAdd()" it will call this function. This in turn adds an entry to the todoList in your scope. As this array has been modified the angular digest cycle is triggered and the list is updated.
Some suggestions for your questions: First of all, you mean W3Schools, not the W3C (which is a standardization organization and normally is not doing tutorials, which is why I got curios - Also, you will find lots of reasons why not to use W3Schools when goolgin around or looking at meta). Also, if you compare to some other code, you should include it or at least link to it.
I found it by googling and it seems your only change is using <span>{{x.todoText}}</span> instead of <span ng-bind="x.todoText"></span>. There really is no difference in terms of the digest cycle here. The only difference is that by using {{}} it might at first be rendered as curly brackets in the browser window, before the variable is actually replaced. Thus, it is usually better to use ng-bind.

Check $pristine status of ngModel without using a form

Am trying to figure out how to check the state of a ngModel without using a form tag. I don't have wrappers is just basic input element with a ngModel.
All the examples I have found so far are for form validations and in this case, there is no form.
When i tried something like:
HTML
<input type="text" ng-model="lastname">
SCRIPT:
if($scope.lastname.$dirty) {
console.log('last name has changed');
}
I get undefined.
Is there a way to check the state of the ngModel without adding a watch directive to it? it seems it would be something basic that is part of the framework. Why wouldn't this work?
There are two ways:
1. Use ng-form:
<span ng-form="myForm">
<input type="text" name="name" ng-model="name" required/>
</span>
Now you can access the model either at $scope.myForm.namein your controller or with myForm.name in your view:
var isPristine = $scope.myForm.name.$pristine;
2. Use angular.element().controller('ngModel') (Don't do this one, bad bad bad)
Alternatively, you could hack your way around it. But this is going to be ugly, untestable, and gross:
var elem = angular.element(document.getElementById('myElement'));
var model = elem.controller('ngModel');
var isPristine = model.$pristine;
Edit: Your situation (per your comment) inside of a repeater
the only difference between my example and your is that the input field is inside a ng-repeater. Thought that wouldn't matter but I guess it does.
And now it's time to ask yourself what you're doing and why... You can still get the information you need using ng-form, but you'll need to do some crazy stuff I wouldn't recommend:
<div ng-repeater="item in items track by $index">
<span ng-form="rptrForm">
<input type="text" name="name" ng-model="item.name" required/>
</span>
</div>
.. commence craziness:
// get the first child scope (from the repeater)
var child = $scope.$$childHead;
while(child) {
var isPristine = child.rptrForm.$pristine;
var item = child.item;
if(!isPristine) {
// do something with item
}
child = child.$$nextSibling;
}
It's probably time to rethink your strategy
I'm not sure what your end goal is, but you might want to rethink how you're going about it and why. Why do you need programmatic access to $pristine in your controller? What alternatives are there? Etc.
I, for one, would try to leverage an ng-change event and update some flag on my item in my repeater, and leave the ng-form stuff for validation:
<div ng-repeat="item in items track by $index" ng-form="rptrForm">
<input type="text" name="name" ng-model="item.name" ng-change="item.nameChanged = true" required/>
<span ng-show="rptrForm.name.$error.required>Required</span>
</div>
If you give the <form> element a name attribute, then the <form> will be
added to the $scope object as a property.
Field controller will then be attached to the form property.
As weird as it could seem, you have to define an enclosing form with a name attribute like so:
<form name="myForm">
<input type="text" name="lastName" ng-model="lastname">
</form>
and call the property with:
$scope.myForm.lastname.$dirty
Indeed, ngModelController (field) is attached to ngFormController (form).
I used Ben Lesh's answer to deal with the same problem. I was displaying a list of notification preferences, and my goal was to hit my api with models that had changed. Not just ng-changed, either; value changed.
I start by initializing some private state on my controller:
// Private state
var originalModels = {};
var changedModels = [];
Then I store copies of the original models retrieved from the API:
// Create a hash of copies of our incoming data, keyed by the unique Code
for (var i = 0; i <= data.length - 1; i++) {
var np = data[i];
originalModels[np.Code] = angular.copy(np);
}
Next, we want to make sure that when a model changes, we add it to a changed models collection (or remove it from the collection if no real change occurred):
function modelChanged(m) {
var originalModel = originalModels[m.Code];
var hasChanged = !angular.equals(originalModel, m);
// If the model has changed and is not present in our collection of changed models, add it
if (hasChanged && changedModels.indexOf(m) === -1) {
changedModels.push(m);
}
// If the model has not changed and is present in our collection of changed models, remove it
if (!hasChanged && changedModels.indexOf(m) > -1) {
var i = changedModels.indexOf(m);
changedModels.splice(i, 1);
}
}
Finally, we tie these together in our ng-repeat:
<tr ng-repeat="np in npc.notificationPreferences">
<td>{{np.DisplayName}}</td>
<td>
<input type="checkbox"
ng-model="np.Sms"
ng-checked="np.Sms"
ng-disabled="!np.SmsEnabled"
ng-change="npc.modelChanged(np)"/>
</td>
</tr>
Now, instead of pushing back every row to my API, I simply push the changed models. This also has a side benefit of being able to ng-disable your submit button if nothing has actually changed (ie the changedModels collection is empty).

Dynamically create muliple input fields. Submit them into an array

I've created a jsFiddle: check it out
Currently, when creating the scopes they are not isolated. Therefore, if i create two input fields and type in one of them the text is duplicated in the secondary inputfield.
How can I create multiple inputs, fill them out individually and submit them all at the same time?
html
<div ng-app="miniapp" ng-controller="MainCtrl">
<a href="" data-clicker>add inputs</a>
<form ng-model="project" ng-submit="addPage()">
<div class="sections"></div>
<input type="submit" value="submit"/>
</form>
<hr>
<hr>
<p>project: {{project.name | json}}</p>
<p>output: {{output | json}}</p>
</div>
JS
var $scope;
var app = angular.module('miniapp', []);
app.directive('clicker', function($compile) {
'use strict';
return {
compile: function(tElement, tAttrs) {
//var t = '<div data-pop>Pop</div>';
var t = '<div><input type="text" ng-model="project.name"></div>';
return function(scope, iElement) {
iElement.click(function() {
$('.sections').append($compile(t)(scope));
});
};
}
}
});
app.controller('MainCtrl', function($scope) {
$scope.project = {"name":"sup"};
$scope.output = [];
$scope.addPage = function() {
$scope.output.push(_.clone($scope.project));
};
});
I feel like I've tried everything... Is it just a flaw in my logic? If so, can you show me an example that works according to the user flow below?
User Flow
You should use Angular's ng-repeat directive to iterate through an array of objects and generate the input fields with data-binding.
Here is a simplified version of your code using the directive: http://jsfiddle.net/89AYX/42/
With ng-repeat, everything within the block becomes a template that get compiled automatically for each iteration of an array model. Think of it like a for-each loop in HTML that updates itself when the array changes.
<div ng-repeat="project in projects">
<input type="text" ng-model="project.name"/>
</div>
As you can see, the project variable becomes accessible within the block as a reference to an object in the array. You can then use that reference to create a two-way binding on the input field with a property of that particular object.
Angular comes with a lot of useful built-in directives that solves a lot of common issues involving data-binding. Be sure to check out their API reference to see what's available.
whenever you create a new input you are attaching the same model property to it project.name
var t = '<div><input type="text" ng-model="project.name"></div>';
switch that access a different attribute for each. or make an attribute an array if you can do that

Angular progmatically empty an input with Bootstrap typeahead

I'm using a form to add elements to list that is displayed on the side of the form. Markup is:
<form name="stateForm">
<input type="text" ng-model="model.name" typeahead="state for state in states | filter:$viewValue">
<button ng-click="addState(model)">Add to list</button>
</form>
<ul>
<li ng-repeat="state in selected_states">{{state.name}} - {{state.desc}}</li>
</ul>
while the controller is:
$scope.states = ['Alabama', 'Wyoming'];
$scope.selected_states = [];
$scope.addState = function(state){
$scope.selected_states.push(state);
$scope.model = {};
$scope.stateForm.$setPristine();
}
Working sample: http://plnkr.co/edit/sLQCCm?p=preview
Now the problem is that while I can clear every input with $scope.model={} or $scope.stateForm.$setPristine(), nothing clears the input with the typeahead. I suspect this may be because of the way the typeahead directive was implemented.
Is there a way I can achieve this without ditching the typeahead?
Actually there was a bug in the typeahead directive that was preventing your scenario from working correctly. It was already fixed in master and will be part of the next release (0.5.0), hopefully in the coming week.
In the meantime you can grab the latest version from the CI server as shown in this plunk:
http://plnkr.co/edit/j9undd?p=preview

Categories

Resources