Keep Knockout from running isValid() on page load - javascript

I want to add a data attribute (data-invalid) to a text field if it is invalid and remove it if it is valid. Here's the html and js:
<form data-bind="submit: submitFields">
First Name: <input type="text" runat="server" ID="FirstName" data-bind="value: firstName, valueUpdate: 'blur', attr: { 'data-invalid': !firstName.isValid()}"/>
<br/>
<button type="submit" >Submit</button>
</form>
var viewModel;
viewModel = function() {
self = this;
self.firstName = ko.observable().extend({
required: true,
notify: 'always'
});
//sets errors to an array of the error messages?
self.errors = ko.validation.group(this, { deep: true, observable: false })
self.submitFields = function(formElement) {
if (self.errors().length > 0) {
self.errors.showAllMessages();
return;
}
//submit
}
};
ko.applyBindings(new viewModel());
And a fiddle to test it out:
http://jsfiddle.net/frontenderman/HhtEZ/2/
My problem is that firstName.IsValid() runs on pageload. Is there an elegant way to get around this? All I can think of is doing something like subscribing to firstName and using a flag to set the data-attribute to null on the first run through my view model and then returning !firstName.isValid() on any of the subsequent runs.

Related

How to bind focusOut event to knockoutjs

I'm trying to bind focusout event to my knockout js. here is the example:
<div class="form">
<label>
Country:
</label>
<input type="text" id="countryName" name="countryId._autocomplete" data-bind="value: countryName,event: { blur: onBlurCountryEvent }" />
</div>
<div class="form" data-bind="visible: onBlurCountryEvent">
<label>
Time zone:
</label>
<input type="text" id="timeZoneName" name="timeZoneId._autocomplete" data-bind="value: timeZoneName" />
</div>
and this is my knockoutjs:
define(['viewmodels/shell', 'durandal/system', 'durandal/services/logger', 'plugins/router', 'knockout', 'common', 'jqueryform', 'toastr', 'kovalidationconfig'],
function (shell, system, logger, router, ko, common, jqueryform, toastr, kvc) {
var vm = {
activate: activate,
logger: logger,
shell: shell,
countryId: ko.observable(),
countryName: ko.observable(),
timeZoneName: ko.observable(),
timeZoneId: ko.observable(),
timeZoneVisibility: timeZoneVisibility,
bindingComplete: function (view) {
bindFindCountryEvent(view);
bindFindTimeZoneEvent(view);
}
};
vm.onBlurCountryEvent = function () {
var countryVal = $('#countryName').val();
if (countryVal != undefined && countryVal != null && countryVal != '') {
console.log("trueee");
return true;
}
else {
console.log("falseee");
return false;
}
}
function bindFindCountryEvent(view) {
jQuery("#countryName", view).typeahead(
...
}
function bindFindTimeZoneEvent(view) {
jQuery("#timeZoneName", view).typeahead(
...
}
function activate(id) {
shell.isLoading(true);
...
shell.isLoading(false);
});
return true;
}
vm.save = function () {
...
};
});
So, as you can see, I want to have some event and binded function, when I do onBlur from my field country, to check, and to preview timezone field if there any selected country from dropdown search.
Also, if user skips the country, timezone filed should remain visible:false
the event works, and I can see in my console true/false values.
However, my field of timeZone is intact. No matter if this country field is empty or non-empty, the fields is visible always.
If I put visible:false (hardcoded value), it works.
Should I need to bind that function vm.onBlurCountryEvent?
the problem is that the function onBlurCountryEvent is not an observable, so knockout is not checking for changes. I would suggest adding a isTimezoneVisible : ko.observable(false) to your view model then set the isTimeZoneVisible in the onBlurCountryEvent.
In your view set the visible binding to isTimeZoneVisible. Something like the following
var vm = {
countryId: ko.observable(),
countryName: ko.observable(),
timeZoneName: ko.observable(),
timeZoneId: ko.observable(),
isTimeZoneVisible: ko.observable(false), //new property
bindingComplete: function(view) {
bindFindCountryEvent(view);
bindFindTimeZoneEvent(view);
}
};
vm.onBlurCountryEvent = function() {
var countryVal = $('#countryName').val();
if (countryVal != undefined && countryVal != null && countryVal != '') {
console.log("trueee");
vm.isTimeZoneVisible(true); //set property
} else {
console.log("falseee");
vm.isTimeZoneVisible(false); //set property
}
}
function bindFindCountryEvent(view) {
}
function bindFindTimeZoneEvent(view) {
}
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="form">
<label>
Country:
</label>
<input type="text" id="countryName" name="countryId._autocomplete" data-bind="value: countryName,event: { blur: onBlurCountryEvent }" />
</div>
<div class="form" data-bind="visible: isTimeZoneVisible">
<label>
Time zone:
</label>
<input type="text" id="timeZoneName" name="timeZoneId._autocomplete" data-bind="value: timeZoneName" />
</div>

Knockout custom validation: How to check if observable is equal to a specific value?

I'm new with Knockout.js, and I would like to check if a field of my form has a specific value. Actually, I only check if it is required or not. What should I do?
Here's what I have in my html page:
<div data-bind="visible: !Vm.isValid()" class="text-danger">Fill each field to send data, otherwise show this message</div>
<input data-bind="enable: Vm.isValid()" type="button" value="Send data!" />
That's what my vm.js file looks like:
window.Vm = ko.validatedObservable({
name : ko.observable().extend({ required: true })
});
I would make something like this, but I don't know how to do it:
var found = "found";
window.Vm = ko.validatedObservable({
name: ko.observable().extend({
required: true,
function: {
if (this.val() == found)
return true; // invalid value, can't submit my form
}
})
});
I would actually recommend against using the Knockout Validation library, as it hasn't been maintained for years. It's an outdated solution to a problem that doesn't really exist anymore. In 2019 you can just use the form validation that is native to every modern browser. Just put a required attribute on your form fields and the form will not submit if not all required fields have been filled out.
If you want it to be a little more dynamic, you could do something like this:
function ViewModel() {
var vm = this;
vm.name = ko.observable();
vm.required = ['name', 'email'];
vm.isRequired = isRequired;
function isRequired(field) {
return vm.required.indexOf(field) > -1;
}
}
And use the attr binding to set the required attribute based on the array of required elements in your viewmodel.
<input type="text" data-bind="textInput: name, attr: { required: isRequired('name') }">
You can use a custom validator like this (Documentation):
var found = "found";
var Vm = ko.validatedObservable({
name: ko.observable().extend({
required: {
message: "This is a required field",
},
validation: {
validator: (val, paramValue) => {
// "val" has the value entered in the field
// "paramValue" has the value set in "params"
return val === paramValue
},
message: "The value is not " + found,
params: found
}
})
});
ko.applyBindings(Vm)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>
<input type="text" data-bind="value: name" />
I have taken data as ["A","B"], and search based upon the same data.
ko.extenders.required = function(target, overrideMessage) {
//add some sub-observables to our observable
target.hasError = ko.observable();
target.validationMessage = ko.observable();
target.data = ko.observableArray(["A","B"]);
target.found = ko.observable();
target.foundMessage = ko.observable();
//define a function to do validation
function validate(newValue) {
target.hasError(newValue ? false : true);
target.validationMessage(newValue ? "" : overrideMessage || "This field is required");
target.found(target.data().find(function(element){ return newValue==element;}));
target.found()?target.foundMessage("element has found"):target.foundMessage("element has not found");
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
function AppViewModel(first) {
this.firstName = ko.observable(first).extend({ required: "" });
}
ko.applyBindings(new AppViewModel("C"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<p data-bind="css: { error: firstName.hasError }">
<input data-bind='value: firstName, valueUpdate: "afterkeydown"' />
<span data-bind='visible: firstName.hasError, text: firstName.validationMessage'> </span>
<span data-bind='visible: (!firstName.hasError()), text: firstName.foundMessage'> </span>
</p>

Login page in Oracle jet

Hi I am creating an application using Oracle JET in which after I click the Login button in the LoginTest page, it should take me to the Homepage after validation. I have managed to validate the input but I couldn't route it to the Homepage. I have tried using multiple binding but it is of no use. Could someone please help.
HTML CODE
h1>logintest</h1>
<div align="center">
<label for="username">Username</label>
<input id="username" type="text" required
data-bind="ojComponent: {component: 'ojInputText',
validators: [{type: 'regExp', options: {pattern: '[a-zA-Z0-9]{3,}',
messageDetail: 'You must enter at least 3 letters or numbers'}}],
invalidComponentTracker: tracker}" /><br /><br />
<label for="password">Password</label>
<input id="password" type="password" required
data-bind="ojComponent: {component: 'ojInputPassword',
validators: [{type: 'regExp', options : {pattern: '(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{6,}',
messageSummary : '{label} too Weak',
messageDetail: 'The password must contain atleast one uppercase, one lowercase, one number and must be 6 digits long'}}],
invalidComponentTracker: tracker}" /><br /><br />
<a class="oj-button-primary oj-button-xl"
href="http://localhost:8383/Test/index.html?root=home" id="create" type="button"
data-bind="ojComponent: {component: 'ojButton',
label: 'Login',
disabled: shouldDisableCreate()},
click: onClick"></a>
</div>
JAVASCRIPT CODE
define(['ojs/ojcore', 'knockout', 'ojs/ojinputtext', 'ojs/ojbutton', 'ojs/ojknockout-validation', 'ojs/ojmodel'
], function (oj, ko) {
/**
* The view model for the main content view template
*/
function logintestContentViewModel() {
var self = this;
self.tracker = ko.observable();
self.username = ko.observable("");
self.password = ko.observable("");
self.clickedButton = ko.observable();
self.buttonClick = function(data, event)
{
var trackerObj = ko.utils.unwrapObservable(self.tracker);
if (!this._showComponentValidationErrors(trackerObj))
{
return;
}
};
self.routePage = function(data,event)
{
self.clickedButton(event.currentTarget.id);
return true;
};
self.onClick = function()
{
self.buttonClick();
self.routePage();
}
self.shouldDisableCreate = function()
{
var trackerObj = ko.utils.unwrapObservable(self.tracker),
hasInvalidComponents = trackerObj ? trackerObj["invalidShown"] : false;
return hasInvalidComponents;
};
self._showComponentValidationErrors = function (trackerObj)
{
trackerObj.showMessages();
if (trackerObj.focusOnFirstInvalid())
return false;
};
}
return logintestContentViewModel;
});
If you are using ojRouter, then you can simply use
oj.Router.go("route name");
If you're not using ojRouter, then you can use the location object. Something like:
window.location.pathname='/homepage'
I recommend using ojRouter and it's canEnter() method for things like this.
Router cookbook demo:
http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=router&demo=simple
JSDocs for Router canEnter method
http://www.oracle.com/webfolder/technetwork/jet/jsdocs/oj.RouterState.html
You can use
oj.Router.rootInstance.go('homepage');

this.content is not updating with values from template

I have the following template
<script type="text/x-handlebars" id="friends/new">
<label>First Name</label>
{{input value=firstName}}<br />
<label>Last Name</label>
{{input value=lastName}}<br />
<label>About</label>
{{textarea value=about}}<br />
<button {{action "create"}} {{bind-attr disabled=isInvalid}}>Create</button>
</script>
I put data into all the fields and click the create button, which goes to the following controllers
App.FriendsNewRoute = Ember.Route.extend({
model: function(){
return { firstName: "", lastName: "", about: ""}
}
});
App.FriendsNewController = Ember.Controller.extend({
needs: "friends",
isInvalid: true,
validForm: function(){
if(this.get('lastName') && this.get('firstName')){
this.set("isInvalid", false);
} else {
this.set("isInvalid", true);
}
}.observes('firstName','lastName'),
actions: {
create: function(){
var newFriend = Ember.copy(this.content);
console.log(newFriend);
}
}
});
When calling this.get('lastName') ect the content that I have entered into the text boxes is correct. But when I log this.content, the value is still the initial value that I set in the FriendsNewRoute. What do I need to do so that this.content correctly update with the current data in my template?
You should change:
Ember.Controller
to
Ember.ObjectController

Comparing value from input with backbone collection data

I'm trying to create very simple login with backbonejs. Collection stores usernames and passwords. Login view has two inputs and on click it should perform check function and compare input value with data from collection.
Html part looks like this:
<div class="login-block">
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn">Sign in!</button></div>
</form>
</script>
<div class="error" class="block">
Error
</div>
<div class="success">
Success
</div>
</div>
Here is my Js code:
var User = Backbone.Model.extend({
defaults: {
login: 'root',
mail: 'root#mail.com',
password: ''
}
});
var user = new User();
//variable to store username
var loginData = {
username: "",
password: ""
}
// userbase
var UserCollection = Backbone.Collection.extend({
model: User
});
var userCollection = new UserCollection([
{
username: 'Ivan',
mail: 'ivan#mail.com',
password: '1234'
},
{
username: 'test',
mail: 'test#mail.com',
password: 'test'
}
]);
// login page
var LoginView = Backbone.View.extend({
el: $(".login-block"),
events: {
"click .btn": "check"
},
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if (loginData.username === userCollection.each.get("username") && loginData.password === userCollection.each.get("password"))
{appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
},
render: function () {
//$(this.el).html(this.template());
var template = _.template($('#start').html())
$(this.el).html(template());
//template: template('start');
return this;
}
});
var loginView = new LoginView({collection: userCollection});
var AppRouter = Backbone.Router.extend({
routes: {
'': 'index', // start page
'/error': 'error',
'/success': 'success'
},
index: function() {
loginView.render();
console.log("index loaded");
},
error: function(){
alert ('error');
},
success: function(){
console.log('success');
}
});
var appRouter = new AppRouter();
Backbone.history.start();
It works fine to the check function, and it stores username and password, but something is clearly wrong either with router or check function when it starts comparison. Instead of routing to success or error page, it rerenders index page.
P.S I didn't use namespacing and code in general is not of a greatest quality, but it was made for educational purpose only.
You have to add the attribute type="button" to your button, otherwise it will submit the form when clicked (See this question):
<script type="text/template" id="start">
<form id="login">
<div class="input-wrapper"><input type="text" placeholder="Username" id="username" required></div>
<div class="input-wrapper"><input type="password" placeholder="Password" id="password" required></div>
<div class="input-wrapper"><button class="btn" type="button">Sign in!</button></div>
</form>
</script>
You can also return false in the click event handler, which would cancel the default action. (submitting the form, if you don't add type="button").
For comparing the values with the hardcoded collection, you can't call each as you where doing (which is an iteration function provided by Underscore) because you would receive an error. You could use Underscore's findWhere method which is also available in Backbone collections. So the click event handler (Your check function) could look like this:
check: function(){
loginData.username = this.$el.find("#username").val(); // store username
loginData.password = this.$el.find("#password").val();// store password
if(userCollection.findWhere({username: loginData.username, password: loginData.password})){
appRouter.navigate("success", {trigger: true});
}else{
appRouter.navigate("error", {trigger: true});
}
return false;
},
You can try it on this fiddle
The logic check you're doing doesn't look like it would work to me. I would expect the following to generate an error:
userCollection.each.get('username')
the function you're calling on your collection, each, is a wrapped underscore method which takes a function callback as a parameter. If you want to check your username and password, I'd do something like this:
var user = userCollection.findWhere({ username: loginData.userName });
This will return you the model where the username matches. Then you can check the password of that model:
if (user.get('password') === loginData.password) {
// do something
} else {
// do something else
}
EDIT Heck, you can do both checks at once:
var user = userCollection.findWhere({ username: loginData.userName, password: loginData.password });
I'll leave the previous code up just to demonstrate.

Categories

Resources