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).
Related
Working on an update form which I would like to generate and capture inputs for a variable sized array
The current unhappy version only supports the first three statically defined elements in the constituency array. So the inputs look like this...
<input #newConstituency1 class="form-control" value={{legislatorToDisplay?.constituency[0]}}>
<input #newConstituency2 class="form-control" value={{legislatorToDisplay?.constituency[1]}}>
<input #newConstituency3 class="form-control" value={{legislatorToDisplay?.constituency[2]}}>
and the function to update pulls the values of the form using the static octothorpe tags.
updateLegislator(newConstituency1.value, newConstituency2.value, newConstituency3.value)
But this doesn't allow for a variable sized Constituency array.
I am able to use *ngFor directive to dynamically create input fields for a theoretically infinitely sized constituency array:
<div *ngfor constit of legislatorToDisplay?.constituency>
<input value={{constit}}>
</div>
but have not successfully been able to capture that information thereafter. Any kind assistance would be greatly appreciated.
You just have to have a form object in your component that matches the HTML input components that were created.
Template
<div *ngfor constit of legislatorToDisplay?.constituency>
<input value={{constit}} formControlName="{{constit}}">
</div>
Component
/* create an empty form then loop through values and add control
fb is a FormBuilder object. */
let form = this.fb.group({});
for(let const of legislatorToDisplay.constituency) {
form.addControl(new FormControl(const))
}
Use two-way data binding:
<div *ngFor="constit of legislatorToDisplay?.constituency; let i = index">
<input [(ngModel)]="legislatorToDisplay?.constituency[i]">
</div>
I'm trying to clear ng-model inputs, but is not working, and i can't figure out why.
I have:
<button ng-click="$ctrl.clear()"></button>
and in the clear action i have:
$scope.$event = null;
Should work, right?
If i do:
<button ng-click="$event = null"></button>
Will work, but i want avoid this in the HTML.
I already try to use angular.copy, and:
$scope.$event = {};
$scope.$event = '';
But doesn't work and is not giving me any erros message.
Thanks.
UPDATE:
<input type="text" ng-model="$event.title"/>
<input type="text" ng-model="$event.name"/>
<input type="number" ng-model="$event.age"/>
<input type="date" ng-model="$event.date"/>
The problem is in the click handler, $event refers to the event object not your event object in the scope.
Use another name to refer your object. Note that property names starting with $ is normally used by angularjs to refer to its properties, so best we don't use them.
So I am trying to store data to variables on the page when a button is clicked, there are multiple buttons and it needs to store data for that specific button. Each one of these buttons is nested inside a <form onsubmit></form> all the data I need to extra is within this form in different <input> and <div> tags. So using javascript or jquery how can I select the value of a specific and tag. So when the button is clicked on it adds this product to the cart, I want to take the productNumber, price, and quantity and store them to my own variables. When the button is clicked the forum calls onsubmit="ShoppingCartAddAJAX( this ); so I want to store this data in the ShoppingCartAddAJAX function.
Here is what one of the forms on the page looks like.
<form method="post" name="addtocart-501" onsubmit="ShoppingCartAddAJAX( this ); return false;">
<input type="hidden" name="formName" value="ShoppingCartAddAJAX" />
<input type="hidden" name="productNumber" value="758201" />
<input id="qtyproduct" type="hidden" class="hiddenqty" name="dmst_Qty_2805" value="1" />
<div class="price">
<div class="pricenew singleprice">
$7.99
</div>
</div>
</a>
</div>
</div>
</li>
</form>
So I have been trying to get this data by doing something like this.
var pn = $('input[name$="productNumber"]').val();
var qty = $('input[id$="qtyproduct"]').val();
In my javascript file the function looks something like this:
function ShoppingCartAddAJAX(formElement, productNumber) {
var pn = $('formElement.input[name$="productNumber"]').val();
var aa = $('formElement[name$="productNumber"]').val();
var qty = $('input[id$="qtyproduct"]').val();
}
But with alot more code... just showing that the function is passing in formElement and a productNumber.
But this is giving me the product number and quantity of the first product on the page, I need the data for which ever button the user decides to click on and there are multiple ones on the page not just one. I hope that when the button is clicked and that function is fired there is a way to look what forum it came from and then extract that data. I would also like to be able to get the price but it is stored in <div class="pricenew singleprice"> $7.99</div>.
Any help is greatly appreciated.
You are getting the product number and quantity of the first record since you have multiple input fields in the form with the name of productNumber(according to your description). So what basically happens here is when you call $('input[name="productNumber"]').val() jquery returns you the value of the first input field. Instead of that, do something like this inside your ShoppingCartAddAJAX function.
function ShoppingCartAddAJAX(form)
{
// Find child an element inside our form with the id of "qtyproduct"
// I used "find("#qtyproduct")" method so it searches anything nested inside your specific form element
// You can use "children("#qtyproduct")" method also
var qty = $(form).find("#qtyproduct").val();
var pn = $(form).find("input[name='productNumber']").val();
var pp = $(form).find(".pricenew.singleprice").text();
}
The situation:
<body>
<div id="1">
<input type="text" name="email_1"/>
<input type="text" name="email_2"/>
</div>
<div id="2">
<input type="text" name="email_3"/>
<input type="text" name="email_4"/>
</div>
<!--and so on...-->
</body>
And I need to validate inputs inside these 2 inputs inside every div to be equal(only inside div). Maybe the main problem is that all divs are dynamically generated, and we don't know exactly their quantity to provide knockout support. How to do that? What is the most elegant solution?
Update 1
I've tried:
1. To make some binding using knockout model. But my solution for this
was to create some observable property to check inputs values. This
is bad way I guess.
2. To use jquery for this. Tried to validate fields via validate class for
inputs(http://jqueryvalidation.org/jQuery.validator.addClassRules/)
Update 2
My solution was something like that:
<div id="1">
<input type="text" name="email_1"/>
<input type="text" name="email_2"/>
<label data-bind="visible: checkEmailsEquality(email_1,email_2)">Emails must be equal</label>
</div>
But this solution is not ok, because this binding works only once - at page loading, what isn't good. I need to bind this check to text update in these inputs, and I don't know how.
Update 3
My suggestion is to deal with it in this way:
Make on the first email input this binding http://knockoutjs.com/documentation/textinput-binding.html
Bind with similar function in knockout's model as wrote Wayne Ellery.
If values aren't equal make error label visible.
The main condition is to pass apropriate inputs ids to function, and I guess this will work.
The simplest way to do this is to just use a computed which will compare the two emails.
Below in ViewModel I'm using the jQuery $.map method to map all items to an array of Item objects.
var ViewModel = function (model) {
var self = this;
self.items = $.map(model.items, function(item) { return new Item(item) });
};
Here I'm using a computed method in Item to compare email1 and email2.
var Item = function (item) {
var self = this;
self.email1 = ko.observable(item.email1);
self.email2 = ko.observable(item.email2);
self.areEmailsSame = ko.computed(function() {
return self.email1() === self.email2();
});
};
http://jsfiddle.net/pxar0587/1/
I've found maybe the most elegant solution, it's about using equalTo attribute, e.g.:
<div id="1">
<input type="text" id="1" name="email_1" equalTo="#2"/>
<input type="text" id="2" name="email_2" equalTo="#1"/>
</div>
Hope this help somebody.
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.