I have a quick example for Knockout Validation that I'm trying to get working, but for whatever reason isValid() on my validatedObservable is always returning true.
The JS:
var vm = function () {
self = this;
self.val1 = ko.observable('').extend({
required: true
});
self.val2 = ko.observable('').extend({
required: true
});
self.valid = ko.validatedObservable(self);
self.checkValid = function () {
alert(self.valid.isValid());
}
return self;
};
ko.applyBindings(new vm());
The Markup:
<input type="text" id="value1" data-bind="value: val1" />
<input type="text" id="value2" data-bind="value: val2" />
<button data-bind="click: checkValid">Is it valid?</button>
Any ideas as to why self.valid.isValid() always returns true?
Related
I have two checkboxes that I would like to implement following logic:
if both are OFF, that is allowed, if one checkbox is ON, if the other is to be ON (checked) then the first one must toggle to OFF and vice versa
How can I do this with html and ko.js?
This is the code that i got for toggling:
var viewmodel = function(){
var self = this;
self.checkA = ko.observable(true);
self.checkB = ko.pureComputed({
read: function(){
return !self.checkA()
},
write: function(value){
value? self.checkA(false) : self.checkA(true);
},
owner: self
});
};
ko.applyBindings(new viewmodel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
A
<input type="checkbox" data-bind="checked: checkA" />
B
<input type="checkbox" data-bind="checked: checkB" />
Since the value of B isn't merely a function of the value of A (both can be false) you'll have to have a second observable to store its state independently. Then for your logic I recommend using ko.subscribe instead of a computed function to react to the changes to either value.
var viewmodel = function() {
var self = this;
self.checkA = ko.observable(true);
self.checkB = ko.observable(false);
self.checkA.subscribe(function(value) {
if (value) self.checkB(false); //also clear B
});
self.checkB.subscribe(function(value) {
if (value) self.checkA(false); //also clear A
});
};
ko.applyBindings(new viewmodel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
A <input type="checkbox" data-bind="checked: checkA" />
B <input type="checkbox" data-bind="checked: checkB" />
Having three input fields
<input type="text" name="foo1" ng-model="data.input1"/>
<ng-messages for="forms.myForm.foo1" role="alert">
<ng-message when="oneRequired"> Please set foo1 (or foo2 or foo3) </ng-message>
<ng-messages>
<input type="text" name="foo2" ng-model="data.input2"/>
<ng-messages for="forms.myForm.foo2" role="alert">
<ng-message when="oneRequired"> Please set foo2 (or foo1 or foo3) </ng-message>
<ng-messages>
<input type="text" name="foo3" ng-model="data.input3"/>
<ng-messages for="forms.myForm.foo3" role="alert">
<ng-message when="oneRequired"> Please set foo3 (or foo1 or foo2) </ng-message>
<ng-messages>
I want to guarantee that at least one input fields value is set. In this case, not only the current validation fields $error should evaluate to 'false' but also all others. All messages should disappear.
My first idea was to use a directive and a unique id to link the fields together:
<input type="text" name="foo1" ng-model="data.input1" one-required="i1_i2_i3_link_identifier/>
Probably I could use a (singleton) service for the registration of the controller and the current values. But I don't have an idea to ensure that all linked controllers (used in the directives) are updated on validation errors.
I highly recommend using the https://github.com/turinggroup/angular-validator directive. It is very flexible and will easily allow to to set your custom validators. I was able to get rid of ng-messages and clean up my html code greatly with this directive.
You can set a custom validator in your controller or service to use throughout your site.
<input type = "text"
name = "firstName"
class = "form-control"
ng-model = "form.firstName"
validator = "myCustomValidator(form.firstName)"
validate-on="dirty"
required></div>
Here is a plunker and code for you: http://plnkr.co/edit/X5XdYYekT4YZH6xVBftz?p=preview As you can see this is very clunky and there is a lot of code. With angular validator you can reduce your code to be inline within your inputs and add a controller function.
<form name="myForm">
<input type="text" name="foo1" ng-model="data.input1" required/>
<div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
<ng-messages for="myForm.foo1" role="alert">
<ng-message="required"> Please set foo1 (or foo2 or foo3) </ng-message>
</ng-messages>
</div>
<br/>
<input type="text" name="foo2" ng-model="data.input2" required/>
<div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
<ng-messages for="myForm.foo2" role="alert">
<ng-message="required"> Please set foo2 (or foo2 or foo3) </ng-message>
</ng-messages>
</div>
<br/>
<input type="text" name="foo3" ng-model="data.input3" required/>
<div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
<ng-messages for="myForm.foo3" role="alert">
<ng-message="required"> Please set foo3 (or foo2 or foo3) </ng-message>
</ng-messages>
</div>
<br/>
</form>
I solved the problem using a central Service holding the values and a callback registry. The callbacks are called all the time, when the input changes (using a watcher):
angular.module('myApp', []);
angular.module('myApp').controller('myFormController', function($scope) {
$scope.data = {
i1: "remove",
i2: "all",
i3: "values"
};
});
angular.module('myApp').factory('oneRequiredService', function() {
var service = {};
var container = {};
var observerCallbacks = {};
var isValid = function(groupId) {
var valid = false;
var modelStates = container[groupId];
angular.forEach(modelStates, function(modelValid) {
valid = valid || (modelValid ? true : false);
});
return valid;
};
var isRegistered = function(groupId) {
return container.hasOwnProperty(groupId);
};
var notifyAll = function(key) {
var valid = isValid(key);
if (isRegistered(key)) {
angular.forEach(observerCallbacks[key], function(callback, index) {
callback(valid);
});
};
};
service.register = function(groupId, scopeId, callback) {
this.updateValue(groupId, scopeId, undefined);
if (callback) {
this.registerCallback(groupId, callback);
}
};
service.registerCallback = function(groupId, callback) {
if (callback) {
observerCallbacks[groupId] = observerCallbacks[groupId] || [];
observerCallbacks[groupId].push(callback);
};
};
service.updateValue = function(groupId, scopeId, value) {
container[groupId] = container[groupId] || {};
container[groupId][scopeId] = value;
notifyAll(groupId);
};
return service;
});
angular.module('myApp').directive('oneRequired', function(oneRequiredService) {
return {
restrict: "A",
require: 'ngModel',
scope: true,
link: function(scope, element, attrs, ctrl) {
var modelAttr = attrs["ngModel"];
var linkIdentifier = attrs["oneRequired"];
var updateCurrentState = function(isValid) {
scope._valid = isValid;
};
scope.$watch(modelAttr, function(newVal, oldVal) {
oneRequiredService.updateValue(linkIdentifier, scope.$id, newVal);
});
scope.$watch('_valid', function(newVal, oldVal) {
ctrl.$setValidity('oneRequired', newVal);
});
oneRequiredService.register(linkIdentifier, scope.$id, updateCurrentState);
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<body ng-app="myApp">
<form ng-controller="myFormController" name="forms.myForm">
<label for="i_1">i_1</label>
<input id="i_1" name="i1" type="text" one-required="foo-bar" ng-model="data.i1" />
<span> i1-err-one-required: {{forms.myForm.i1.$error.oneRequired}} </span> <br>
<label for="i_2">i_2</label>
<input id="i_2" name="i2" type="text" one-required="foo-bar" ng-model="data.i2"/>
<span> i2 err-one-required: {{forms.myForm.i2.$error.oneRequired}} </span> <br>
<label for="i_3">i_3</label>
<input id="i_3" name="i3" type="text" one-required="foo-bar" ng-model="data.i3"/>
<span> i3-err-one-required: {{forms.myForm.i3.$error.oneRequired}} </span> <br>
</form>
</body>
This code does a search and displays the results. I want to conditionally disable inputs.I am using knockout to control when an input is disabled.
Requirement:
input is disabled when set by the system.
User input should not be disabled.
In the following code input that comes from readDatabase() works well. If the user types in the Hometown/Nickname inputthen tabs out then input disables. How can I fix this code to meet the second requirment?
Update
I am not opposed to getting some help from jQuery. I do not want to throw out my view model binding entirely.
Fiddle
HTML
Name: <input type="text" data-bind="value: userInput, valueUpdate: 'input'" /><br />
Hometown: <input type="text" data-bind="value: Hometown, disable: Hometown" /><br />
NickName: <input type="text" data-bind="value: Address, disable: Address" />
JavaScript
function MyViewModel() {
var self = this;
self.userInput = ko.observable();
self.Hometown = ko.observable();
self.Address = ko.observable();
self.userInput.subscribe(function () {
readDatabase(self);
});
}
ko.applyBindings(new MyViewModel());
function readDatabase(self){
if(self.userInput().substring(0,1) == "a"){
self.Hometown("A town");
self.Address("A address");
}
else {
self.Hometown("");
self.Address("Other address");
}
}
You could use an extender to provide a flag indicating where the data came from:
ko.extenders.isServerSet = function (target, value) {
target.isServerSet = ko.observable(value);
return target;
};
function MyViewModel() {
var self = this;
self.userInput = ko.observable();
self.Hometown = ko.observable().extend({
isServerSet: false
});;
self.Address = ko.observable().extend({
isServerSet: false
});;
self.userInput.subscribe(function () {
readDatabase(self);
});
}
ko.applyBindings(new MyViewModel());
function readDatabase(self) {
if (self.userInput().substring(0, 1) == "a") {
// don't overwrite user-provided values
if (!self.Hometown()) {
self.Hometown("A town");
self.Hometown.isServerSet(true);
}
if (!self.Address()) {
self.Address("A address");
self.Address.isServerSet(true);
}
} else {
self.Hometown("");
self.Address("Other address");
}
}
Name: <input type="text" data-bind="value: userInput, valueUpdate: 'input'" /><br />
Hometown: <input type="text" data-bind="value: Hometown, disable: Hometown.isServerSet" /><br />
NickName: <input type="text" data-bind="value: Address, disable: Address.isServerSet" />
Updated fiddle
I have the following HTML:
<select id="EmpName" data-bind="value: Employee.EmpName, event: { change: $root.updateEmployee }"></select>
<input disabled type="text" id="EmpNum" data-bind="value: Employee.EmpNum, valueUpdate: 'input'" />
<input disabled type="text" id="EmpClass" data-bind="value: Employee.EmpClass, valueUpdate: 'input'" />
<input disabled type="text" id="EmpDept" data-bind="value: Employee.EmpDept, valueUpdate: 'input' " />
<input disabled type="text" id="EmpStat" data-bind="value: Employee.EmpStat, valueUpdate: 'input'" />
And it's bound by the following ViewModel:
generalViewModel = function (thisData) {
var self = this;
this.Incident = ko.mapping.fromJS(thisData.Incident);
this.Employee = ko.mapping.fromJS(thisData.Employee);
this.updateEmployee = function () {
var employeeName = self.Employee.EmpName;
$.getJSON('/Incidents/GetEmployee', { EmployeeName: employeeName }, function (data, status, xhr) {
var newEmp = ko.mapping.fromJS(data);
self.Employee(newEmp);
});
}
this.refreshData = function (incID) {
GetIncidentGeneralInfo(incID, node);
}
this.savetoServer = function (incID, buttonID) {
var incident = ko.toJSON(self.Incident);
var employee = ko.toJSON(self.Employee);
$.post('/Incidents/SaveIncident', { IncidentID: incID, JSONIncident: incident, JSONEmployee: employee, button: buttonID }, function (data, status, xhr) {
self.refreshData(data);
});
}
}
ko.applyBindings(new generalViewModel(data), document.getElementById(node));
Everything is working quite nicely, with the exception of the updateEmployee function. The JSON is returning the new Employee information, but the textboxes aren't updating. I'm doing something silly incorrectly, and I can't quite figure out wat
Instead of
var newEmp = ko.mapping.fromJS(data);
self.Employee(newEmp);
You should do
ko.mapping.fromJS(data, self.Employee);
This will update all of the observable properties on self.Employee that were created by the first call to ko.mapping.fromJS.
I have a simple html page with value input and save button.
I want the save will be enabled only if the value is changed (somtimes is initialized and somtimes not.
I've tryied few things without any success
HTML
<input type="text"
placeholder="type here"
data-bind="value: rate,"/>
<button data-bind="click: save">Save</button>
JS
var viewmodel = function () {
this.rate = ko.observable('88').extend(required: true);
};
viewmodel.prototype.save = function () {
alert('save should be possible only if rate is changed);
};
Also on jsfiddle
Should be able to achieve this with a computed observable and the enable binding.
See http://jsfiddle.net/brendonparker/xhLrB/1/
Javascript:
var ctor = function () {
var self = this;
self.originalRate = '88';
self.rate = ko.observable('');
self.canSave = ko.computed(function(){
return self.originalRate == self.rate();
});
};
ctor.prototype.save = function () {
alert('save should be possible only if rate is changed');
};
ko.applyBindings(new ctor());
HTML:
<input type="text" placeholder="type here" data-bind="value: rate, valueUpdate: 'afterkeydown'"/>
<button data-bind="click: save, enable: canSave">Save</button>