I'm working on a Durandal JS SPA application, and I wish to use Knockout Validation.
The problem is that validation is being triggered on page load, which is not what I want - I'd like to trigger the validation myself.
I have tried using
ko.validation.init({
registerExtenders: true,
messagesOnModified: true,
insertMessages: false
});
as well as ko.validation.configure with the same parameters, followed by ko.validation.init();
Here is a snippet of my viewmodel.
function ViewModel(){
var self = this;
self.username = ko.observable().extend({
required: true
});
self.errors = ko.validation.group(self);
}
I also tried delaying the call to ko.validation.group(self) till a button is clicked, but then it wont validate at all.
assuming you only want to show a summary of validation errors per some action, you could do something like this:
html
<input type="text" data-bind='value: username' />
<br/>
<button data-bind="click: submit">Submit</button>
<div data-bind="visible: showErrors, text: errors" />
js
function ViewModel() {
var self = this;
self.username = ko.observable().extend({
required: true
});
self.showErrors = ko.observable(false);
self.submit = function () {
self.showErrors(true);
if (self.isValid()) {
// save data here
}
}
self.errors = ko.validation.group(self);
}
sample fiddle
http://jsfiddle.net/LvHUD/1/
Related
I want to enable/disable a kendo combobox based on the user's selection from a checkbox, which I am storing in a variable.
I already tried setting the variable to the enable property, but this is useful only when the control is being built-in.
Does anybody know If I can do this while creating the control?
<div id="fund" class="col-xs-3">
input class="required" data-bind="title: $parent.selectedFund,
kendoComboBox: {
placeholder: 'Start typing to search...',
value: $parent.test,
widget: $parent.searchSource,
dataTextField: 'managerName',
dataValueField: 'managerId',
filter: 'contains',
autoBind: false,
minLength: 3,
enable: overrideGlobalMapping, //this does not work for me even though the variable holds the correct value
change: function(){ if(this.value() && this.selectedIndex == -1)
{
setTimeout(function () {$parent.selectedManagerId(null);}, 100);}},
dataSource: {
serverFiltering: true,
transport: {
read: $parent.retrieveManager
}
}
}" />
</div>
I ended up wrapping the kendo combox definition in a function, so it now looks likes this:
<input type="checkbox" id="overrideGlobalMappingCheck" onclick="SetFundsCombobox()" data-bind="checked: overrideGlobalMapping, enable: $root.canConfirmMapping" />
The kendo combobox is still wrapped and has an id, which I later use to manipulate it in javascript:
<div class="col-xs-3" id="funds">
<input class="required" data-bind="title: $parent.selectedFund,
kendoComboBox: {
placeholder: 'Start typing to search...',
value: $parent.selectedManagerId,
...
}" />
</div>
And this is the JavaScript function bound to the onclick checkbox's event:
function SetFundsCombobox() {
var fundsDiv = document.getElementById('funds');
var inputSelector = fundsDiv.getElementsByClassName('k-input');
var span = fundsDiv.getElementsByTagName('span');
if (document.getElementById('overrideGlobalMappingCheck').checked) {
document.getElementById('funds').disabled = false;
inputSelector[0].disabled = false;
span[1].classList.remove("k-state-disabled");
} else {
document.getElementById('funds').disabled = true;
inputSelector[0].disabled = true;
span[1].classList.add("k-state-disabled");
}
};
I'd have rather preferred to perform this via the view model, but it works for now.
EDIT:
I've been able to do this the right way (following the MVVM pattern), so now rather than wrapping the kendo combo box in a function, I added the following function in the view model:
$scope.overrideGlobalMappingChecker = ko.computed(function () {
if ($scope.entityMapping()) {
var checkboxChecked = $scope.entityMapping().overrideGlobalMapping();
$("#funds .k-input").prop('disabled', !checkboxChecked);
if (!checkboxChecked) {
$scope.selectedFundId(null);
}
}
});
So now, what the html only needs is the definition of the id in the div containing the combo box:
<div class="col-xs-3" id="funds">
<input data-bind="title: $parent.selectedFundName, kendoComboBox: {
autoBind: false,
...
}" />
</div>
And that's it, it's a much cleaner/correct way to handle this.
I have model in a page that is bound to several controls. Based on some condition some of these controls will be visible or invisible. And on the final submit I should only validate those which are visible.
The following is a sample code to explain my requirement
<script src="knockout-3.4.0.js" type="text/javascript"></script>
<input type="checkbox" data-bind="checked:requireAge" >Age Required</input><br />
Name : <input data-bind="value:Name" /><br />
<div data-bind="visible:requireAge">
Age: <input data-bind="value:Age,visible:requireAge" />
</div>
<button type="button" onclick="validateModel();">Validate</button>
<script type="text/javascript">
var viewModel = { Name: ko.observable(), Age: ko.observable(),requireAge:ko.observable(false) };
ko.applyBindings(viewModel);
function validateModel() {
//validate visible properties and throw a common message that all visible fields should be filled
}
</script>
My suggestion is to use the knockout-validation library (you made no mention of it in your question so I assume you're not using it already) It ties in seamlessly with knockout and makes validation far more convenient. I've used it extensively over the past year and its make my life a whole lot easier. No need to create computeds to keep track of whether an observable contains a valid value or not. You can find the knockout-validation library on github.
In your case you can simply do the following:
var viewModel = function(){
var self = this;
self.name = ko.observable();
self.requireAge = ko.observable(false);
self.age = ko.observable().extend({
required: {
onlyIf: function() { return self.requireAge(); }
}
});
};
Validation error messages are inserted automatically below the element the observable is bound to. You can also create your own validation rules but there are many that work straight out the box including the one demonstrated above. You can even use some data attributes for some rules. This is probably the best way to go about validation in conjunction with knockout.
Based on some condition some of these controls will be visible or invisible.
It would be better if these conditions are contained in the model. And validation method too.
See snippet:
var viewModel = function() {
this.Name = ko.observable("");
this.Age = ko.observable("");
this.requireAge = ko.observable(false);
this.isValid = ko.computed(function() {
if (ko.unwrap(this.Name).length === 0) return false;
if (ko.unwrap(this.requireAge) &&
ko.unwrap(this.Age).length === 0) return false;
return true;
}, this);
};
window.onload = function() {
ko.applyBindings(new viewModel());
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="checkbox" data-bind="checked:requireAge" >Age Required</input><br />
Name : <input data-bind="value:Name" /><br />
<div data-bind="visible:requireAge">
Age: <input data-bind="value:Age,visible:requireAge" />
</div>
<div>is valid: <span data-bind="text: isValid"></span></div>
I am implementing knockout validation for my form, and I want to make a field required only if it is showing. Depending on the selection of other fields in the form, some controls may be hidden by visibility:hidden or display:none. How can I make these fields required only if shown? I have tried this
var name = ko.observable().extend({
required: {
onlyIf: function () {
return ($('#name').is(':visible'));
},
message: '*** Required'
}
});
But it doesn't seem to work, and I'm not sure if it should (can you write custom logic like that in knockout onlyIf params?).
Thanks for any help.
As mentioned in comments all you need to do is
Declare a observable in ViewModel per say self.nameVisible=ko.observbale() set the value it True/False from anywhere (either from DB or based on other control selection) . Later on you should use the self.nameVisible() in your validations i.e with .extend & onlyIf combo to make things(hide/show element+dynamic conditional validation) work .
Html:
<input type="text" data-bind="value:name,visible:nameVisible"/>
viewModel:
var ViewModel = function () {
var self = this;
self.nameVisible = ko.observable(true); //Set it dynamically
self.name = ko.observable().extend({
required: {
message: '*** Required',
onlyIf: self.nameVisible
}
});
};
ko.applyBindings(new viewModel());
Sorry for keep asking this, but I just can't figure it out. I've reduced the question to just the bare minimum.
How can I validate a dynamically generated form? Below is my attempt, but as seen, it shows up as passing validation.
https://jsfiddle.net/j2pgobze/1/
<form id="myForm">
<input type="text" name="email" id="email" value="bademail" >
</form>
<button id="validate">validate</button>
var myValidateObj = {
rules: {
email: {
email: true
}
}
};
$(function () {
jQuery.validator.setDefaults({
debug: true,
success: "valid"
});
$('#validate').click(function () {
//Validate the traditional form
var validate1 = $('#myForm').validate(myValidateObj);
console.log('Option 1', $('#myForm'), $('#email'), $('#email').val(), validate1.element('#email'), $('#email').valid(), $('#myForm').valid());
//Validate dynamically created form
var input = $('<input />', {
type: 'text',
name: 'email',
value: 'bademail'
});
//input.prop('value', 'bademail');
var form = $('<form />').append(input);
var validate = form.validate(myValidateObj);
console.log('Option 2', form, input, $('#email').val(), validate.element(input), input.valid(), form.valid());
});
});
The button needs to be inside the form and be a type="submit" in order for the plugin to capture the click.
Do not put .validate() within a click handler (See item 1). It's only used to initialize the plugin on a form. Exception, below we are creating the new form within a click handler and then immediately calling .validate() on the new form.
With these two small changes, the validation on the static form is working: jsfiddle.net/j2pgobze/3/
I rewrote your DOM manipulation code for clarity. I simply duplicated the HTML for the form and gave it a new ID: http://jsfiddle.net/zem84tfp/
$(function () {
// INITIALIZE plugin on the traditional form
var validate1 = $('#myForm').validate(myValidateObj);
$('#newform').one('click', function () {
// code here to create new form; give it new ID
// do not duplicate ID on anything else
// INITIALIZE plugin on the new form
var validate = $('#myForm2').validate(myValidateObj);
});
});
I'm a little confused by validation in angular. All of the validation appears to be bound to the form. But what do you do in cases where the controller needs to know if the model is valid or not?
I've knocked up and example:
HTML
<div ng-app="stpApp">
<div id="multiStop" class="fullBottomContent" ng-controller="multiStopController">
<ul class="journey">
<li ng-repeat="journey in inboundJourney">
<ng-form name="journeyForm">
<span>
<input type="text" class="AirportName" name="DepartureAirport" ng-model="journey.DepartureAirport" ng-required="true" />
<span ng-show="journeyForm.DepartureAirport.$error.required">Invalid</span>
</ng-form>
<a class="removeJourney" ng-click="removeInboundJourney($index)" href="javascript:void(0)">Remove</a>
</li>
</ul>
<span ng-show="valid()">this is all valid</span>
<span ng-click="addInboundJourney()" title="Add a journey">+</span>
</div>
</div>
JavaScript
var stpApp = angular.module('stpApp', []);
stpApp.controller('multiStopController', function ($scope, $compile, $http) {
$scope.showAddButton = true;
$scope.dataLoaded = false;
$scope.inboundJourney = [
{ 'DepartureAirport': '',
'DestinationAirport': '',
'DepartureDate': '',
'DepartureTime': '9',
'Class': 'All'
},
{ 'DepartureAirport': 'Test1',
'DestinationAirport': '',
'DepartureDate': '',
'DepartureTime': '9',
'Class': 'All'
}
];
$scope.valid = function() {
//how do I test validity here?
return true;
}
$scope.addInboundJourney = function () {
$scope.inboundJourney.push({ 'DepartureAirport': '',
'DestinationAirport': '',
'DepartureDate': '',
'DepartureTime': 9,
'Class': ''
});
}
$scope.removeInboundJourney = function (index) {
$scope.inboundJourney.splice(index, 1);
}
});
Fiddle here
So I want my valid() function to return true or false depending on whether the data in the model is valid or not. I've tried journeyForm.$valid, $scope.journeyForm.$valid and $scope.journeyFormDepartureAirport.$valid. None of which work.
I can't figure out how to check $valid from within my controller. Especially being as I have a variable number of forms.
Also should the controller have knowledge of the forms? That's in the view?
I think the issue is that the validation all resides within the view. I'm guessing that angular has no concept of an invalid model. It's just data. But, well, that's a problem for me. I want to know that the model meets all of me criteria (added into the view, such as ng-required) before I perform an action in the controller.
To explain the comment clearer on the use of $valid
<form name="myform" >
<input type="text" name="input1" ng-required="true"/>
....
JS
$scope.valid = function() {
return $scope.myform.$valid; /* how to return $valid for 'myform' form */
}
Could you use a $watch
$scope.valid = false;
$scope.$watch('inboundJourney', function(){
var isValid = false;
//set isValid depending on inboundJourney
$scope.valid = isValid;
});
Then set it as the show condition
<span ng-show="valid">this is all valid</span>
I figured it out, Rob helped quite a bit actually. My problem was the placement of the form:
<li ng-repeat="journey in inboundJourney">
<ng-form name="journeyForm">
Angular did not seem to like the form within the ng-repeat. I changed it to:
<ng-form name="journeyForm">
<ul class="journey">
<li ng-repeat="journey in inboundJourney">
i.e. the form is outside of the ng-repeat and then did as Rob suggested,
$scope.valid = function() {
return $scope.journeyForm.$valid; /* how to return $valid for 'myform' form */
}
and it all worked
Fiddle
To moan a little this did not complain about the invalid syntax. From my limited experience of angular this is a constant issue, it's got a nasty habit of failing silently. Needz moar error pleaz