I've been reading many questions that ask how to call a knockout validation extender on a button click event. But all the answers that come close to answering the question, involve workarounds using the knockout-validate library. I'm not using the knockout-validate library. All I want to do is validate an input field on a button click event using the validation rules defined in a knockout extender.
For simplicity I'm going to use the required extender from the knockout documentation and apply it to a simple use case. This use case takes an input and on a button click event does something with the value the user entered. Or updates the view to show the validation message if no value was entered.
Knockout Code:
ko.extenders.required = function (target, overrideMessage) {
target.hasError = ko.observable();
target.validationMessage = ko.observable();
function validate(value) {
target.hasError(value ? false : true);
target.validationMessage(value ? "" : overrideMessage || 'Value required');
}
return target;
};
function MyViewModel() {
self = this;
self.userInput = ko.observable().extend({ required: 'Please enter a value' });
/**
* Event handler for the button click event
*/
self.processInput = function () {
//Validate input field
//How to call the validate function in the required extender?
//If passes validation, do something with the input value
doSomething(self.userInput());
};
}
Markup:
<input type="text" data-bind="value: userInput, valueUpdate: 'afterkeydown'" />
<span data-bind="visible: userInput .hasError, text: userInput .validationMessage" class="text-red"></span>
<button data-bind="click: processInput ">Do Something</button>
How can I trigger the validation on the button click event and show the validation message if it doesn't pass validation?
I believe you were looking at the example here - http://knockoutjs.com/documentation/extenders.html
You can not call validate directly, but the subscribe watches the value and runs the validate function on change and updates an observable you can check - hasError.
ko.extenders.required = function (target, overrideMessage) {
target.hasError = ko.observable();
target.validationMessage = ko.observable();
function validate(value) {
target.hasError(value ? false : true);
target.validationMessage(value ? "" : overrideMessage || 'Value required');
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
function MyViewModel() {
self = this;
self.userInput = ko.observable().extend({ required: 'Please enter a value' });
/**
* Event handler for the button click event
*/
self.processInput = function () {
if(self.userInput.hasError()){
console.log('has error');
}else{
console.log('no error');
}
};
}
// Activates knockout.js
ko.applyBindings(new MyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="value: userInput, valueUpdate: 'afterkeydown'" />
<span data-bind="visible: userInput .hasError, text: userInput .validationMessage" class="text-red"></span>
<button data-bind="click: processInput ">Do Something</button>
Related
Is there a way to extend an observable to make it required only if and observable is true, and that changes can be tracked?
For example, I got this custom extender:
ko.extenders.requiredOnlyIf = function (target, makeRequired) {
target.HasError = ko.observable();
function validate(newValue) {
if (makeRequired) {
target.HasError(newValue ? false : true);
} else {
target.HasError(false);
}
}
validate(target());
target.subscribe(validate);
return target;
}
And this observables:
self.Field1 = ko.observable().extend({ requiredOnlyIf : self.Field2() });
self.Field2 = ko.observable().extend({ requiredOnlyIf : self.Field1() });
This observables are dependant, so, if one is filled, the other must be filled too. But the extender only works fine when the value is binding the first time, but when the value of any of the observables is changed is not working.
A couple of things:
You need to pass the observables, not the observables' contents, when setting up the extensions. (You can't create Field1 based on Field2 before Field2 exists, either.)
You need two subscriptions, so that if a change to one makes the other invalid, that is noticed.
(update) Rather than subscriptions and an observable, you can use a computed
ko.extenders.requiredOnlyIf = function(target, makeRequired) {
target.HasError = ko.pureComputed(() => {
const otherHasValue = !!makeRequired();
const targetHasValue = !!target();
return otherHasValue && !targetHasValue;
});
return target;
}
self = {};
self.Field1 = ko.observable();
self.Field2 = ko.observable().extend({
requiredOnlyIf: self.Field1
});
self.Field1.extend({
requiredOnlyIf: self.Field2
});
ko.applyBindings(self);
.invalid {
border-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input data-bind="value: Field1, css: {invalid: Field1.HasError()}" />
<input data-bind="value: Field2, css: {invalid: Field2.HasError()}" />
Is there a way to use checkValidity() setCustomValidity() within ember? For example, in my controller upon submission I have:
var inpObj = this.get('name');
if (inpObj.checkValidity() == false) {
alert('ok');
}
and of course this is my handlebar code:
{{input id="name" type="text" value=name placeholder="Your Name" required="true"}}
Upon submission of this, I get this error message:
inpObj.checkValidity is not a function
You would need to get HTML5 element instead of string to call checkValidity function.
var inpObj = this.get('name'); // this is only a string
You can use jQuery instead:
var inpObj = Ember.$('#name')[0];
if (inpObj.checkValidity() == false) {
alert('ok');
}
Working demo.
If you want to avoid jQuery, you could set an action on your submit button that runs the valid check for you.
<button {{action "submitForm"}}>Your Button</button>
Then have an action in your contoller:
actions: {
submitForm() {
var inpObj = this.get('name');
if(!inpObj.checkValidity()) {
// error handling
alert('ok');
} else {
// save your data, or whatever you need to do
this.transitionTo('some.route');
}
}
}
I have created a Backgrid table to manage users. One of the functions of that table is to allow paswords to be changed by an administrator. This is achieved by adding a button cell column to the backgrid table that launches a modal change password dialog. On entering the passwords and clicking change, the new password is passed back to the backgrid cell and inserted to the backbone model in the callback.
The problem is passing the callback, because the change password dialog is a compiled client side Jade template, so I can't pass a function in the options when the html is rendered, I can only pass a function name.
Here is what I have so far (showing only the Jade and the Backgrid PasswordCell definition).
The client side Jade template:
#user-passwd-dialog.modal
.modal-dialog
.modal-content
.modal-header.bg-warning
button.close(type="button" data-dismiss="modal" aria-label="Close")
span(aria-hidden="true") ×
h4.modal-title
span.glyphicon.glyphicon-log-in
span Set User Password
.modal-body
if message.length > 0
// show any messages that come back with authentication
.alert.alert-info #{message}
// LOGIN FORM
form
.form-group
label(for="user-password") Password
input.form-control#user-password(type="password" placeholder="New Password")
.form-group
label(for="user-confirm") Confirm Password
input.form-control#user-confirm(type="password" disabled="disabled" placeholder="Confirm Password")
.modal-footer
button.btn.btn-warning.btn-lg#user-change-password(type="button" disabled="disabled") Change Password
button.btn.btn-warning.btn-lg(type="button" data-dismiss='modal') Cancel
script(type="text/javascript").
function checkMatch() {
var password = $("#user-password").val();
var confirmPassword = $("#user-confirm").val();
if (password !== confirmPassword) {
$("#user-confirm").removeClass("alert alert-success").addClass("alert alert-danger");
$("#user-change-password").prop("disabled", true);
return false;
} else {
$("#user-confirm").removeClass("alert alert-danger").addClass("alert alert-success");
$("#user-change-password").prop("disabled", false);
return true;
}
}
$("#user-password").keyup(function() {
var password = $("#user-password").val();
if (password.length >= #{ minLen }) {
$("#user-confirm").prop("disabled", false);
checkMatch();
} else {
$("#user-confirm").prop("disabled", true);
}
});
$("#user-confirm").keyup(function() {
var password = $("#user-password").val();
if (password.length >= #{ minLen }) {
checkMatch();
}
});
$("#user-change-password").click(function() {
var password = $("#user-password").val();
#{result}(password);
$('#user-passwd-dialog').modal('hide');
});
The Backgrid cell is defined as (the compiled Jade template is Templates.set_password(opts)):
var PasswordCell = Backgrid.Cell.extend({
className: "button-cell",
template: function () {
return '<button class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-lock"></span></button>';
},
events: {
"click": "setPassword"
},
setPassword: function (e) {
e.preventDefault();
// XXX binding to global variable so modal can set password
// must be more elegant way to do this
mycallbackwiththelongname = (function(password) {
this.model.set({ password : password});
}).bind(this);
var opts = {
message:"The password must be at least 8 characters long",
minLen: 8,
result: "mycallbackwiththelongname"
};
$("#dialog-wrapper").html(Templates.set_password(opts));
$("#user-passwd-dialog").modal({ keyboard: true });
},
render: function () {
this.$el.html(this.template());
this.delegateEvents();
return this;
}
});
The question is in the code: Is there a more elegant way to pass the callback such that a global function is not needed. A local function would be preferable, but I am not sure how to specify the name.
I have a simplified jsfiddle working using the global function.
I figured out a way to use a function local to the PasswordCell 'click' handler by passing it using the jQuery data API. I attach the callback function to the parent element and then pass the name of the parent element to the function that renders the Jade template.
In the PasswordCell change setPasswd to:
setPassword: function (e) {
e.preventDefault();
var passwordResult = (function(password) {
this.model.set({ password : password});
}).bind(this);
var opts = {
name: "change-password-modal",
message:"The password must be at least 8 characters long",
minLen: 8,
parent: "dialog-wrapper"
};
$("#dialog-wrapper").html(Templates.set_password(opts));
$("#dialog-wrapper").data("onChange", passwordResult);
$("#user-passwd-dialog").modal({ keyboard: true });
},
In the Jade template change the button click event handler:
$("#user-change-password").click(function() {
var password = $("#user-password").val();
$("##{ parent }").data("onChange")(password);
$('#user-passwd-dialog').modal('hide');
});
The jsfiddle has been updated.
I have an empty form and a dropdown list, where on each click, different elements will be attached to the form. I called JQuery validate() once to initialize the validator and defined the validation rules everytime the form is filled up using rules.add().
The required validation worked if I type something in the form, erase it, and click submit. But if I don't give any inputs and immediately click submit, the form don't give an error message saying the inputs are required. It worked only when I set a breakpoint in Chrome.
Did I miss something?
ASPX page
<div id="divMenuQuery">
<div id="divQueryDDL"></div>
<div id="divQueryControls">
<form id="formQuery" />
</div>
<input type="button" id="btnExecuteQuery" runat="server" value="Execute" />
</div>
JS - The example below is only for textbox, but it happens for all HTML controls (DDL, Listbox, ...)
function _init() {
$form = $("#formQuery");
_createDDL(); // creates DDL and registers event handler below
}
function _onChangeDDL() {
var selectedTheme = $(this).val;
$form.validate();
$.each(selectedTheme.Controls, function (index, control) {
_buildControl(index, control);
}
}
function _buildControl(control) {
switch (control.Type) {
case Gon.Control.textBox:
_createTextBox(control);
break;
default:
break;
}
}
function _createTextBox(control) {
var $textBox = $("<input>");
$textBox.attr({
id: control.UniqueID,
name: control.UniqueID,
value: ""
}).appendTo($form);
$textBox.rules("add", {
required: true,
messages: {
required: "This field is required"
}
});
}
function _onClickExecute() {
var numErrors = $form.validate().numberOfInvalids(); // always gives 0
var formNodes = $form.serializeArray();
if (numErrors == 0 && $form.valid()) { // .valid() always gives true
_showQueryResults(formNodes);
}
}
Here is the rendered HTML page:
I would like to show error message on user button click (in case user open page and click on directly just on button).
But Visible state workin just if user edit fields
How to fire methods to change visible state ?
<body>
<input type="text" data-bind="value: can" id="txtcan" />
<span ID="lblCANerror" data-bind="visible:(viewModel.can()=='')" class="error">Mesasage 1</span>
<input type="text" data-bind="value: login" id="txtusername" />
<span ID="lblUsernameError" data-bind="visible:(viewModel.login()=='')" class="error">Mesasage 2</span>
<input type="password" data-bind="value: password" name="txtpassword" />
<span ID="lblPasswordError" data-bind="visible:(viewModel.password()=='')" class="error">Mesasage 3</span>
<button ID="lnkLogin" data-bind="click: ClickBtn"> Click</button>
</body>
<script type='text/javascript'>
var ViewModel = function () {
this.can = ko.observable();
this.login = ko.observable();
this.password = ko.observable();
this.isValidForm = ko.computed(function () {
return ($.trim(this.can) != "") && ($.trim(this.login) != "") && ($.trim(this.password) != "");
}, this);
this.ClickBtn = function(data, e)
{
if (!this.isValidForm())
{
e.stopImmediatePropagation();
};
};
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
</script>
<style type='text/css'>
.error
{
color: #FF0000;
}
</style>
I don't want write to write code for change span visible state manually (like if () then span.show) is it possible to use just knockoutjs FW ?
I have tried subscribe to event with JQuery but result is the same.
$().ready(function () {
$("#lnkLogin").click(function (event) {
if (!viewModel.isValidForm()) {
event.preventDefault();
};
})
});
Thanks.
Remove user defined error span it is not needed.
option 1 (recommended)
1.) import ko validation js.
2.)extend validation
this.can = ko.observable().extend({required:true});
3.)set initial show validation error msg == false
4.) set value == true to show error
Check this fiddle how to show validation error msg when button click
Option2
1.)Add another observable
this.showError = ko.observable(false);
2.)modify condition
data-bind="visible:(can()=='' && showError())"
3.)Changes in click
$().ready(function () {
$("#lnkLogin").click(function (event) {
//check contions here
if(!true){
viewModel.showError(true); // to show error msg
}
if (!viewModel.isValidForm()) {
event.preventDefault();
};
})
});