I'm writing a fairly complicated search form in Angular.
The form is broken into sections - you can search by ID, url, text string or location. When you search by text string and location at the same time, I need to validate that you submit latitude and longitude together.
In other words, searching by a text string is fine. Searching by lat / lng is fine. Searching by a text string and latitude but omitting longitude is not ok.
Whenever possible, I'm using HTML5 and angular directives for validating individual fields' contents, but I'm trying to validate a particular combination of values by using a scope watcher, looking at the form object, and using $setValidity() if I discover that the current search mode is incompatible with a particular combination of fields.
My current issue is that, once I've used $setValidity() once, that validation state is "stuck". When the user switches out of 'textOrLocation' search mode, I want to let angular go back to its default validation behavior. I don't understand why it's not doing that - I only call $setValidity() on scope change after checking the form's in 'textOrLocation' mode.
Javascript:
$scope.search = {
mode: 'id'
};
$scope.$watch(textOrLocationValid, function() {});
function textOrLocationValid() {
var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
if (usingTextOrLocation) {
var textModel = $scope.form.searchText || {},
textValid = textModel.$valid,
textValue = textModel.$modelValue,
latModel = $scope.form.searchLat || {},
latValid = latModel.$valid,
latValue = latModel.$modelValue,
lngModel = $scope.form.searchLng || {},
lngValid = lngModel.$valid,
lngValue = lngModel.$modelValue,
formValid = (textValid && latValid && lngValid) && // No invalid fields
((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
(textValue || latValue); // Either text or location are filled out
if (formValid) {
// Explicitly set form validity to true
$scope.form.$setValidity('textOrLocation', true);
} else {
// Explicitly set form validity to false
$scope.form.$setValidity('textOrLocation', false);
}
}
}
HTML
<form name="form">
<div ng-if="search.mode == 'id'">
<input type="text" name="searchId" required>
</div>
<div ng-if="search.mode == 'textOrLocation'">
<input type="text" name="searchText">
<input type="number" name="searchLat" min="-90" max="90" step="0.000001">
<input type="number" name="searchLng" min="-180" max="180" step="0.000001">
</div>
<button ng-disabled="form.$invalid">Submit</button>
</form>
My understanding is that because the function is being watched, it's actually being evaluated by Angular periodically during each digest. A simple solution might be to set the validity of textOrLocation to true when that particular form is not in focus. This would allow the button state to depend on the validity of the field in the id form.
function textOrLocationValid() {
var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
if (usingTextOrLocation) {
var textModel = $scope.form.searchText || {},
textValid = textModel.$valid,
textValue = textModel.$modelValue,
latModel = $scope.form.searchLat || {},
latValid = latModel.$valid,
latValue = latModel.$modelValue,
lngModel = $scope.form.searchLng || {},
lngValid = lngModel.$valid,
lngValue = lngModel.$modelValue,
formValid = (textValid && latValid && lngValid) && // No invalid fields
((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
(textValue || latValue); // Either text or location are filled out
if (formValid) {
// Explicitly set form validity to true
$scope.form.$setValidity('textOrLocation', true);
} else {
// Explicitly set form validity to false
$scope.form.$setValidity('textOrLocation', false);
}
}
else{
// Explicitly set form validity to true because form is not active
$scope.form.$setValidity('textOrLocation', true);
}
}
To create complex tests, you can use a small directive use-form-error, which may also be useful to you in the future.
With this directive, you can write:
<div ng-form="myForm">
<div>
<input type="text" ng-model="searchText" name="searchText">
<input type="number" ng-model="searchLat" name="searchLat" min="-90" max="90" step="0.000001">
<input type="number" ng-model="searchLng" name="searchLng" min="-180" max="180" step="0.000001">
<span use-form-error="textOrLocation" use-error-expression="textOrLocationValid()" use-error-input="myForm"></span>
</div>
{{myForm.$error}}
<br>
<button ng-disabled="myForm.$invalid">Submit</button>
</div>
And JS
angular.module('ExampleApp', ['use']).controller('ExampleController', function($scope) {
$scope.search = {
mode: 'textOrLocation'
};
$scope.textOrLocationValid = function() {
var usingTextOrLocation = $scope.search.mode == 'textOrLocation';
if (usingTextOrLocation) {
var textModel = $scope.myForm.searchText || {},
textValid = textModel.$valid,
textValue = textModel.$modelValue,
latModel = $scope.myForm.searchLat || {},
latValid = latModel.$valid,
latValue = latModel.$modelValue,
lngModel = $scope.myForm.searchLng || {},
lngValid = lngModel.$valid,
lngValue = lngModel.$modelValue,
formValid = (textValid && latValid && lngValid) && // No invalid fields
((latValue && 1 || 0) + (lngValue && 1 || 0) != 1) && // Either both lat and long have values, or neither do
(textValue || latValue); // Either text or location are filled out
return !formValid;
} else {
// Explicitly set form validity to true because form is not active
return false;
}
}
});
Live example on jsfiddle:
I got it working, you can find code in this Plunker
There were several problems (I'm making assumption, you are trying handling your form in controller, not in custom directive):
$scope.form that we are trying to access in controller is not same form that we have in the view. Form gets its own scope. which is not directly accessible in controller. To fix this, we can attach form to $scope.forms - object, that is declared in controller (read more on inheritance patterns here)
we should attach ng-model to inputs, so we can $watch them, since it not possible to watch form directly (read more here)
in addition, we have to watch $scope.search changes.
It's definitely not the most elegant solution to handle custom validation... Will try to come up with custom directive for that.
I am facing an issue with using ng-init and assign it model inside my html.
The following code works fine. The following code is for Add/Edit functionality. For example, when row is is opened in Edit mode than it persist existing value and shows it in textbox.
<div>
<div ng-if="title == 'Add Student'">
<input type="text" name="name"placeholder="Student Name" data-ng-model="registration.Student.FirstName" maxlength="50">
</div>
<div ng-if="title == 'Edit Student'">
<input type="text" name="name"placeholder="Student Name" data-ng-model="student.Student.FirstName" maxlength="50">
</div>
</div>
However, the following code which is short version of above code does not work. I mean when the row is opened in edit mode it shows text field but does not show existing value (first name) in it. Why?
<div ng-init="model = (title == 'Add Student' ? registration.Student : student.Student)">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Please suggest whether ng-init can't be used in this way or some issue in my code?
thanks
Controller
var currentState = $state.current.name;
if if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
}
You are ng-init block is wrong currently it is returning true or false, you are messing with brackets.
Markup
<div ng-init="model = (title == 'Add Student') ? registration.Student : student.Student">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Update
In your current case you ng-init is getting executed while element rendered on the DOM, at that instance on time registration.Student & student.Student doesn't have any value. Evaluation of ng-init setting null object to the model student. I'd suggest you do set model value from the controller logic that would be more safer.
Code
var currentState = $state.current.name;
if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
//shifted logic in controller
$scope.model = (title == 'Add Student' ? registration.Student : student.Student);
}
Markup
<div>
<input type="text" name="name" placeholder="Student Name"
data-ng-model="model.FirstName" maxlength="50"/>
</div>
Other way you could add one more flag like loadedData which will says that ajax response has been fetched & registration.Student & student.Student values are available in the scope.
Markup
<div ng-if="loadedData">
<input type="text" name="name" placeholder="Student Name" data-ng-model="model.FirstName" maxlength="50">
</div>
Code
var currentState = $state.current.name;
if (currentState == "Add")
{
$scope.registration = {
Student: {
FirstName: '',
}
var _init = function () {
}
$scope.title = " Add Student";
}
else
{
$scope.student= {};
$scope.student= response[0];
var _init = function () {
$scope.title = " Edit Student";
}
//set flag
$scope.loadedData = true;
}
Making Basic Web Application using knockout js.
I have 3 textboxes:
1. price,
2. quantity,
3. total.
I want to calculate total automatically when user add data in quantity & in price.
html code :
price:<input type="text" data-bind="value : itemPrice"><br/>
qty:<input type="text" data-bind="value : itemQTY"><br/>
total:<input type="text" data-bind="value : itemTotal">
knockout js code :
ViewModel = function() {
var self = this;
self.itemPrice = ko.observable();
self.itemQTY = ko.observable();
self.itemTotal = ko.observable();
};
Suggestion are greatly appreciated.
Use ko.computed :
self.itemTotal = ko.computed(function(){
if(isNaN(self.itemPrice()) == true && isNaN(self.itemQTY()) == true || isNaN(self.itemQTY()) == true || isNaN(self.itemPrice()) == true){
return '0.00';
}else{
return self.itemPrice()*self.itemQTY();
}
});
above condition will check that if user has entered text data, if user has entered text data then it ll automatically set to 0.00 otherwise calculate total on base of input then show in total textbox.
& make total textbox readonly so user can't edit it.
Like :
<input type="text" data-bind="value : itemTotal" readonly>
Use ko.computed
self.itemTotal = ko.computed(function() {
return self.itemPrice()*self.itemQTY();
});
I have a html with this template:
<template name="entryfield">
<input type="text" id="name" placeholder="Name" /> <input type="text" id="message" placeholder="Your Message" /><br>
<input type="button" class="plusbutt" value="+"/><input type="button" class="minusbutt" value="-"/>
</template>
and I want to declare the 2 buttons event:
Template.entryfield.events = {
'click .plusbutt': function() {
// Submit the form
var name = document.getElementById('name');
var mood = document.getElementById('mood');
var message = document.getElementById('message');
if(name.value != '' && message.value != ''){
Messages.insert({
name: name.value,
mood: '+',
message: message.value,
time: Date.now()
});
name.value = '';
message.value = '';
}
}
}
'click .minusbutt': function() {
// Submit the form
var name = document.getElementById('name');
var mood = document.getElementById('mood');
var message = document.getElementById('message');
if(name.value != '' && message.value != ''){
Messages.insert({
name: name.value,
mood: '-',
message: message.value,
time: Date.now()
});
name.value = '';
message.value = '';
}
}
But I have error:
chat.js:58:19: Unexpected token :
Which line 58 is the beginning of the second event declaration. But when I totally take out second event, everything just work. My question is how do people declare more than 1 function in the same template events? I seen it everywhere but I just cannot get this to work. Thanks!
Where can I find reference on the template event? The problem on learning Meteor is their functions is from all over, some from MongoDB, Some from Handlebar, and Some from JavaScripts, which make it rather hard to pick up for beginner.
Your events declaration syntax is wrong, be careful !
Be sure to check the Meteor docs, everything is detailed : http://docs.meteor.com/#template_events
It should be like this :
Template.entryfield.events({
"click .plusbutt":function(){
/* ... */
},
"click .minusbutt":function(){
/* ... */
}
});
Template.myTemplate.events is a function which takes an object as parameter.
Each members of the object should be a key/value pair, the key being the event declaration and the value the corresponding handler.
How do you have conditional binding based on other properties?
Example..
var ViewModel = {
IsAdded = ko.observable(),
AddedBy = ko.observable()
}
When I display it.. I don't want to show AddedBy if IsAddedBy is null or false
Something like this..
<input type="text" data-bind="value: if (IsAdded != null && IsAdded) { AddedBy }"/>
I know that isn't right, but something like that...
What I would do is this;
var ViewModel = function() {
this.IsAdded = ko.observable('True');
this.AddedBy = ko.observable('Test');
this.AddedByText = ko.computed(function(){
if ( this.AddedBy() != null && this.IsAdded() ) return this.AddedBy()
return "";
}, this);
}
Then your input would be
<input type="text" data-bind="value: AddedByText" />
This way you are keeping the logic contained within your ViewModel and separate from the HTML.
This question is old but it might help someone else looking
<input type="text" data-bind="value: IsAdded ? AddedBy : "" "/>
Basically if IsAdded is not null then set the value to AddedBy, else do nothing