I have a list of validatedObservables defined. For instance one of them:
self.CompanyName = ko.observable().extend({ required: true });
self.ContactPerson = ko.observable().extend({ required: true });
self.step1Validation = ko.validatedObservable([
self.CompanyName,
self.ContactPerson
]);
I have also other validators except "required" one. For instance, email validator. When user enters incorrect email and moves to another field, then red error message appears. This means that error message are generated and appear nearby controls.
However, when I try to validate all validatedObservable at once, then error message doesn't appear nearby controls. How to fix this?
The validation looks like this (this is self function):
if (self.step1Validation.isValid()) {
return true;
} else {
// SHOULD SOMEHOW SHOW ALL ERROR MESSAGE FOR THIS STEP (step1Validation)
return false; // this doens't allow user to move to next step in wizard
}
EDIT
Here is some simplified jsfiddler example: http://jsfiddle.net/ng73s0hq/1/
In this example, if you add incorrect email and move to another field, then you can see "red error message". But if you press "submit" then validation fails, but there is no error messages (should be required failure + email validation failure).
All you need to do is on click you should call this self.step1Validation.errors.showAllMessages() to show error messages .
Simplified version viewModel:
ko.validation.init({
insertMessages: false
});
var patterns = {
email: /^([\d\w-\.]+#([\d\w-]+\.)+[\w]{2,4})?$/
};
var ViewModel = function () {
var self = this;
self.CompanyName = ko.observable();
self.ContactPerson = ko.observable();
self.Email = ko.observable();
self.test = ko.observable();
self.step1Validation = ko.validatedObservable([
self.CompanyName.extend({
required: true
}),
self.ContactPerson.extend({
required: true
}),
self.Email.extend({
required: true,
pattern: {
message: 'Must be a valid email',
params: patterns.email
}
})]);
self.clickDone = function () {
if (self.step1Validation.isValid()) {
self.test('valid');
return true;
} else {
self.test('invalid');
self.step1Validation.errors.showAllMessages()
return false;
};
};
};
var model = new ViewModel();
ko.applyBindings(model);
working sample here
You need to first validate your ko.validatedObservable object, and if invalid then use the showAllMessages(true) method to display validation errors.
var result = ko.validation.group(self.step1Validation, {deep: true});
if (self.step1Validation.isValid()) {
return true;
} else {
result.showAllMessages(true);
// SHOULD SOMEHOW SHOW ALL ERROR MESSAGE FOR THIS STEP (step1Validation)
return false; // this doens't allow user to move to next step in wizard
}
Related
So I have a custom action (an approval button) that can be triggered either when editing a single entity or for multiple entities when triggered from the entity list view.
The logic essentially removes one entity type and creates a different one with more fields and options available after approval.
The logic works just fine when it is triggered from the Form where there is just a single entity being edited.
However when the same logic is run from the list view and now I am iterating over multiple entities, there is in internal server error when I try to create the record for the new entity type (the one with more options). This makes no sense, I am calling out to a function which already works under a different scenario. And I am creating a new entity, not updating or deleting an existing one so there should be not locks or other concurrency issues.
The the error is gloriously uninformative and I can't see any logs anywhere that would help me debug this. Has anyone run into this before?
Edit
I have enabled CRM Trace Logging for errors (in the registry), however this does not help. This internal server 'Error' does not seem to be errory enough to show up in the logs.
Edit 2
Perhaps some code? The error happens on the SDK.REST.createRecord line, but only when it is run inside the loop of the click handler, when it is run from single entity form, it creates a record without issue.
PCL.EventParticipants = {
EventFormApproveParticipantClick: function (selectedItemIds, entityTypeName) {
debugger;
var anEventRequest,
requestId,
action,
event,
contact,
emailPref,
actualEmail;
console.log('Approval Clicked');
// Do this if we are working on a single event request
if (entityTypeName == null)
{
requestId = Xrm.Page.data.entity.getId();
action = Xrm.Page.data.entity.attributes.get("pcl_action").getValue();
var participant = PCL.EventParticipants.MakeParticipant(
Xrm.Page.data.entity.attributes.get("pcl_contact").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_event").getValue()[0].id,
Xrm.Page.data.entity.attributes.get("pcl_name").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_emailpreference").getValue(),
Xrm.Page.data.entity.attributes.get("pcl_selectedemail").getValue()
);
if (PCL.EventParticipants.Act(requestId, action, participant)) {
alert('Approval complete.');
}
return;
}
var opSuccess = true;
// When multiple requests are selected do...
for (var x = 0; x < selectedItemIds.length; x++) {
requestId = selectedItemIds[x];
SDK.REST.retrieveRecord(
requestId,
"pcl_eventrequest",
"pcl_eventrequestId,pcl_Action,pcl_Contact,pcl_Event,pcl_name,pcl_EmailPreference,pcl_SelectedEmail", null,
function (anEventRequest) {
requestId = anEventRequest.pcl_eventrequestId;
action = anEventRequest.pcl_Action.Value;
var participant = PCL.EventParticipants.MakeParticipant(
anEventRequest.pcl_Contact.Id,
anEventRequest.pcl_Event.Id,
anEventRequest.pcl_name,
anEventRequest.pcl_EmailPreference,
anEventRequest.pcl_SelectedEmail
);
if (!PCL.EventParticipants.Act(requestId, action, participant)) {
opSuccess = false;
}
},
function(error) {
alert('Could not retrieve selected event request: ' + requestId + ' Check that it has not been removed from the system. --> ' + error.message);
}, false
);
}
if (opSuccess) {
alert('Approvals completed.');
} else {
alert('One or more Approvals failed.');
}
},
Act: function (requestId, actionValue, participant) {
var opSuccess = false;
if (actionValue == '798330000') {
// Add action
opSuccess = PCL.EventParticipants.CreateEventParticipant(participant);
}
if (actionValue == '798330001') {
// Remove action
opSuccess = PCL.EventParticipants.RemoveEventParticipant(participant);
}
if (opSuccess == false) {
return opSuccess;
}
opSuccess = PCL.EventParticipants.RemoveParticipantRequest(requestId);
return opSuccess
},
CreateEventParticipant: function (eventParticipant) {
var existingParticipant = PCL.EventParticipants.RetrieveEventParticipantLike(eventParticipant.pcl_Event.Id, eventParticipant.pcl_Contact.Id);
if (existingParticipant != null) {
alert('Cannot approve this request. This contact is already participating in the selected event.');
return false;
}
var opSuccess = false;
SDK.REST.createRecord(
eventParticipant,
"pcl_eventparticipant",
function (result) {
opSuccess = true;
},
function(error) {
alert('Could not create event request with contactId: ' + eventParticipant.pcl_Contact.Id + ' and eventId: ' + eventParticipant.pcl_Event.Id + '. --> ' + error.message);
}, false
);
return opSuccess;
}, .....
}
Edit 3
I have modified the SDK.REST to have a 5th parameter which toggles whether or not the operation is synchronous or asynchronous. Passing false at the end of any operation makes the operation synchronous.
I'm passing a value as a parameter to a component.
<badge-button params="badge: oarBadge"></badge-button>
Here is the viewModel containing oarBadge:
function AppViewModel() {
var self = this;
self.oarBadge = ko.observable();
$.getJSON('/guy.json', function(data) {
var badge = new Badge('wood oar', data.badges.oar, false);
self.oarBadge(badge);
// self.oarBadge().has() returns true so the badge is being properly created with data
// returned by the ajax call
});
} // AppViewModel()
Here is the Badge constructor:
function Badge(name, has, active) {
var self = this;
self.name = ko.observable(name);
self.has = ko.observable(has);
self.active = ko.observable(active);
self.disabled = ko.computed(function() {
return self.has();
});
self.toggleActive = function() {
self.active(!self.active())
};
self.toggleHas = function() {
self.has(!self.has());
};
}
Here is the component's viewModel:
ko.components.register('badge-button', {
viewModel: function(params) {
var self = this;
self.badge = params.badge();
self.open = function() {
self.badge.toggleHas();
self.badge.toggleActive();
}
},
template:
'<img class="ui image" src="http://fakeimg.pl/300/" data-bind="click: open, css: { disabled: badge.disabled }" >'
});
When the page loads, I get an error telling me that badge is undefined.
Full example: https://gist.github.com/guyjacks/5a8763ff71f90e3fe8b4b153ed9a5283
Try setting a default object before the ajax call is completed, also you should assign the observable itself not the evaluation for the observable, so instead of doing this:
self.badge = params.badge();
You should do it like this:
self.badge = params.badge;
Otherwise your variable won't be updated once the ajax request is completed.
Here is a small example: https://jsfiddle.net/b0bdru1u/1/
Note: As far as I know the disable binding won't work in images
I have a viewmodel and there I have properties which are extended to use validation. I call ko.validation.group(self) but this doesn't add the isValid() method to the viewmodel.
So I get an error that isValid() is undefined.
Here is my code:
var brechtbaekelandt = brechtbaekelandt || {};
brechtbaekelandt.login = (function ($, jQuery, ko, undefined) {
"use strict";
function LoginViewModel() {
var self = this;
self.userName = ko.observable();
self.password = ko.observable();
self.rememberMe = ko.observable();
self.errorMessage = ko.observable();
self.userName.extend({ required: { message: 'Please enter your username' } });
self.password.extend({ required: { message: 'Please enter your password' } });
self.errors = ko.validation.group(self);
};
LoginViewModel.prototype.login = function () {
var self = this;
self.errorMessage(null);
alert('entering login');
// self.isValid() is not a function
if (!self.isValid()) {
alert('login invalid');
self.errors.showAllMessages();
return;
}
else
{
alert('login valid');
// do login
}
};
function init() {
alert('entering init');
var knockoutValidationSettings = {
insertMessages: false,
decorateElement: true,
decorateElementOnModified: true,
decorateInputElement: true,
//errorMessageClass: 'error',
//errorElementClass: 'error',
//errorClass: 'error',
errorsAsTitle: false,
parseInputAttributes: false,
messagesOnModified: true,
messageTemplate: null,
grouping: { deep: true, observable: true }
};
ko.validation.init(knockoutValidationSettings, true);
var viewModel = new LoginViewModel();
ko.applyBindingsWithValidation(viewModel);
}
return {
LoginViewModel: LoginViewModel,
init: init
};
})($, jQuery, ko);
I have created a js fiddle: click here
I've read somewhere that you need to call registerExtenders() but I tried it and it doesn't work either.
Can someone help me in the right direction? Thx!
well you seem to be looking for isValid when using group tough there is a way (alternate way) using length property to achieve it . As isValid doesn't seem to be available when using group (it exists with validatedObservable) .
As #jeff mentioned in one of this answers on this topic
The ko.validation.group just gives you an (computed) observable of all
the error messages in a model. It only collects error messages of
direct properties of the model.
The ko.validatedObservable on the other hand not only collects the
error messages, but also wraps the model in an observable and adds an
isValid property which indicates whether or not there are any error
messages (i.e., the model was completely valid). Otherwise, they're
essentially the same.
I modified your code accordingly like below
self.errors = ko.validation.group(self); //It will group error messages in array i.e based on count you must validate
LoginViewModel.prototype.login = function () {
var self = this;
self.errorMessage(null);
//self.isValid() doesn't exist here . so you should base length
if (self.errors().length>0) {
alert('login invalid');
self.errors.showAllMessages();
return;
}
};
working sample with group
working sample with ValidatedObservable Preferable way Imho
I'm using Kendo MVVM and I have a kendo numerictextbox bound to a kendo observable.
All I want is: when the user changes value, a confirm should pop saying something like 'are you sure?' if yes -> no problem, go on.
if no -> NOTHING should happen!
In theory it sounds simple as that... but I found 3 major issues:
1) numerictextbox only got 2 events: spin and change... so any idea of using keypress/focus/or any other event is discarded.
2) So tried using the change event... but I can't preventDefault! Another try was to save previous value and restore it back in case of 'no answer' but this brings me to trigger event change TWICE!
3) Any other model field who is 'observing' the numerictextbox will change before I even answer the confirm box... And I absolutely don't want this!
P.S. I also got a dropdownlist and a datepicker that must work in the same way!
Help please!
Provided a fast example: http://dojo.telerik.com/EyItE
Here you can see how the numericbox2 (who is observing numericbox1 and is computed) changes itself before the user answer yes/no (problem 3)
and keypress/focus/preventDefault doesn't work.
here is an answer about binding events not supported by default:
Kendo MVVM and binding or extending custom events
For preventDefault (or "reverting" the value). I tried to store the previous value as you suggested and it is does not fire twice:
var viewModel = kendo.observable({
myItem: {
// fields, etc
myNumericBox: 10,
myNumericBox2: function () {
return viewModel.get("myItem.myNumericBox")*2;
},
tmp: 10
},
onChange: function (e) {
if ( confirm("are you sure?")) {
viewModel.set("myItem.tmp", viewModel.get("myItem.myNumericBox"));
}
else {
viewModel.set("myItem.myNumericBox", viewModel.get("myItem.tmp"));
}
},
tryf: function () {
alert("hello!"); // doesn't trigger
},
tryk: function() {
alert("hello2!"); // doesn't trigger
}
});
I solved with a custom binding that ask you a confirm between html widget change -> model update.
kendo.data.binders.widget.valueConfirm = kendo.data.Binder.extend({
init: function (widget, bindings, options) { // start
kendo.data.Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
this._change = $.proxy(this.change, this);
this.widget.bind("change", this._change); // observe
},
refresh: function () { // when model change
if (!this._initChange) {
var widget = this.widget;
var value = this.bindings.valueConfirm.get(); // value of the model
if (widget.ns == ".kendoDropDownList") { // for the dropdown i have to use select
widget.select(function (d) {
return d.id == value.id;
});
}
else widget.value(value); // update widget
}
},
change: function () { // when html item change
var widget = this.widget;
if (widget.ns == ".kendoDropDownList") var value = widget.dataItem(); // for dropdown i need dataitem
else var value = widget.value();
var old = this.bindings.valueConfirm.get();
this._initChange = true;
// I want to bypass the confirm if the value is not valid (for example after 1st load with blank values).
if (old == null || old == 'undefined' || old == 'NaN') this.bindings.valueConfirm.set(value); // Update the View-Model
else {
if (confirm("Are you sure?")) {
this.bindings.valueConfirm.set(value); // Update the View-Model
}
else {
this._initChange = false;
this.refresh(); // Reset old value
}
}
this._initChange = false;
},
destroy: function () { // dunno if this is useful
this.widget.unbind("change", this._change);
}
});
I have two dropdowns on an form. If the user selects a value from the first dropdown, I want to make sure they make a choice from the second dropdown. I'm new to knockout, so I'm not sure how to do this. I don't think I need a fullblown validation library. I just want to stop the action so the popup doesn't go away and some text appears saying "hey you, pick something!"
The row looks like this:
<div class="channelRow" data-bind="foreach: channels">
<div class="channelPrompt">CHANNEL: </div>
<select class="channelSelect" name="channels" data-bind="options: $root.financialVM.channelOptions(), value: name, optionsCaption: '--'"></select>
<div class="portPrompt">NUMBER OF PORTS: </div>
<select class="portSelect" name="ports" data-bind="options: $root.financialVM.portOptions(), value: port, optionsCaption: '--'"></select>
</div>
Update:
Here's the function that I'm working with. Edge is a modal window that appears and is where the select boxes are.
function Edge (siteA, siteB, key, channelpair) {
var edge = this;
edge.siteA = ko.observable(siteA);
edge.siteB = ko.observable(siteB);
edge.distance = ko.observable(0);
edge.key = ko.observable(key);
edge.channels = ko.observableArray([
new ChannelPair()
]);
edge.addChannel = function () {
if(edge.channels().length >= 3) return;
edge.channels.push(new ChannelPair());
};
}
function ChannelPair () {
var channel = this;
channel.name = ko.observable();
channel.port = ko.observable(0);
channel.port.extend({
required: {
message: "You can not have a name without a port",
onlyIf: function () { return (self.name() != null); }
}
});
}
Ok here is a complete solution for you:
// You need this config in order to kick things off
ko.validation.configure({
messagesOnModified: true,
insertMessages: true
});
function Edge (siteA, siteB, key, channelpair) {
var edge = this;
edge.siteA = ko.observable(siteA);
edge.siteB = ko.observable(siteB);
edge.distance = ko.observable(0);
edge.key = ko.observable(key);
edge.channels = ko.observableArray([
new ChannelPair()
]);
edge.addChannel = function () {
if(edge.channels().length >= 3) return;
edge.channels.push(new ChannelPair());
};
}
function ChannelPair () {
var channel = this;
channel.name = ko.observable();
// Extending happens on declaration not after wards
channel.port = ko.observable(0).extend({
required: {
message: "You can not have a name without a port",
// self doesn't mean anything here so replace it with channel like this
onlyIf: function () { return (channel.name() != null); }
}
});
// you need a property called errors to keep your validation group in it
channel.errors = ko.validation.group(channel);
}
Also remember to add knockout validation to your project. Its a plugin and not shipped as part of knockout out of the box.