Knockout validation beyond message display - javascript

I am working on KnockOut validation and so far so good. I do have a question though. I have some code like the following:
shippingMethodModel.Description.extend({ required: true });
And that shows a validation message below BUT does it set a flag or something which I can read to disable my save button?

I had this same need recently, so I'll try to translate what I did based on the line of code you provided above...
Try adding a ko.computed observable similar to the following:
shippingMethodModel.formIsNotValid = ko.computed(function () {
// original line
// var errors = ko.utils.unwrapObservable(ko.validation.group(self));
// ** oops, not "self" in this case
// UPDATED line
var errors = ko.utils.unwrapObservable(ko.validation.group(shippingMethodModel));
return (errors.length > 0);
});
UPDATE
I made a correction in the code above after noticing my error.
For those declaring such a model/class as a function all at once, this code may look similar to the following:
var ShippingMethodModel = function () {
var self = this;
self.shippingMethodId = ko.observable().extend({ required: true });
self.description = ko.observable().extend({ required: true });
self.formIsNotValid = ko.computed(function () {
var errors = ko.utils.unwrapObservable(ko.validation.group(self));
return (errors.length > 0);
});
};
/UPDATE
UPDATE2
Based on input from #ericb in the comments below, I made a change to the way I'm implementing my own solution, which I'll demonstrate by adapting the example code in my update above:
var ShippingMethodModel = function () {
var self = this;
self.shippingMethodId = ko.observable().extend({ required: true });
self.description = ko.observable().extend({ required: true });
self.formIsNotValid = ko.observable(false);
self.validateObservableFormField = function (nameOfObservableToValidate,
data, event) {
for (var prop in data) {
if (prop === nameOfObservableToValidate) {
var theObservable = data[prop];
theObservable.valueHasMutated();
ko.validation.validateObservable(theObservable);
if (theObservable.error) {
self.formIsNotValid(true);
}
else {
if (self.formIsNotValid()) {
var errors =
ko.utils.unwrapObservable(ko.validation.group(self));
self.formIsNotValid(errors.length > 0);
}
}
return;
}
}
};
};
Notice that I've now defined formIsNotValid as an observable, but I'm using the validateObservableFormField function to help me with pre-submit form validity determination. This change ensures that the ko.validation.group function is called only as needed, and that call should only be needed when the observable being validated is valid, but formIsNotValid is true (to handle the case where that current observable was the one that had set formIsNotValid to true).
Here's an example of how I'm doing this:
<input data-bind="value: description,
event: { blur: function(data, event) {
validateObservableFormField('facilityName',
data,
event)
}
}" />
goofy formatting to eliminate horizontal scroll
NOTE: I was already using this technique, but I've adapted it to improve the performance of checking whether or not the form is valid. #Californicated, I realized after my last comment that calling this function from the blur event of validated form fields is why I was seeing my save/submit button toggle between enabled and disabled states.
Thanks again to #ericb for the performance tip.
Further tips, from anyone, are always welcome!
/UPDATE2
Once you've got that in place, disabling the button is a matter of binding to that formIsNotValid computed observable in whatever way makes sense for how you intend to disable the button, e.g. css: { 'ui-state-disabled': formIsNotValid } and/or disable: formIsNotValid and/or some other method(s).
Hope this helps, and let me know if you run into trouble.

I would setup the following:
saveEnabled = ko.computed(function(){
// possible other logic
return shippingMethodModel.Description.isValid();
});
and then in your HTML:
<button data-bind="enable: saveEnabled"> Save </button>
Or, if you have multiple properties on your model, you could do something like:
ko.validation.group(shippingMethodModel);
and then in your HTML:
<button data-bind="enable: isValid"> Save </button>
the group function adds an isValid property to whatever it groups.

Related

How to handle two forms on one page for validation?

I have two forms that get displayed alternatively in a popup, one for editing (which is loaded by ajax), one for creation.
I want to use jquery validation to display hints both on editing and on submission of fields.
Some of the validation involves time spans which must not overlap.
This works for creation, but the new rule I am creating for editing does not get triggered because the rule for creation takes precedence.
I have added class rules, because the rules I created by name or id somehow don't get active at all, maybe because I am confused how the jquery validate plugin works. These rules are commented out in the code snippet.
var classRules = {};
classRules['thresholdStartTime'] = {};
classRules['thresholdStartTime'][globalThresholdCheckName] = $form
classRules['thresholdEndTime'] = {};
classRules['thresholdEndTime'][globalThresholdCheckName] = $form
/*
*
for(var q = 0; q<20; ++q) {
validationrules['thresholdStartTime' + q] = {};
validationrules['thresholdStartTime' + q]['required'] = true;
validationrules['thresholdStartTime' + q][globalThresholdCheckName] = $form
validationrules['thresholdEndTime' + q] = {};
validationrules['thresholdEndTime' + q]['required'] = true;
validationrules['thresholdEndTime' + q][globalThresholdCheckName] = $form
}
*/
for(var cls in classRules) {
var rule = classRules[cls];
$.validator.addClassRules(cls, rule);
}
//return validationrules as rm.rules here
//in the caller, get validationrules from result
var editForm = $('#editForm')
.validate(
{
rules : rm.rules,
classRules: rm.classRules,
messages : rm.messages,
success : $.noop
});
Make the form's default action be prevent e.preventDefault(), then submit using jQuery.ajax(), choosing what data to send, then bind that ajax request to the form's submit button. Do this for each form:
jQuery('.bidvalue').click(function(e) {
e.preventDefault();
jQuery.ajax({
type: 'POST',
url: ajaxurl,
data: 'action=newbid&id='+e.target.name,
success: function(msg){
jQuery('#vehicle-value-box'+e.target.name).html(msg+',00€');
}
});
EDIT, as I completely missed the actual question...
For each form, you can specify validation as below:
$('#myform1').validate({
rules: {
myfield1: {
required: true,
minlength: 3
},
myfield2: {
required: true,
email: true
}
}
});
but you'll have to be more specific and show the HTML of both forms, and maybe what validation rules you'd like, for me to really answer your question helpfully.
EDIT #2: To add some stuff form the comments (kudos to Vicente Olivert Riera for input).
If you wish to toggle which form to apply validation upon, do this:
var activeForm;
$("form").focus(e => {
e.validate({
rules: {
myfield1: {
required: true,
minlength: 3
},
myfield2: {
required: true,
email: true
}
}
});
});
If you have many forms and just want specific forms matching certain criteria only:
$("form").focus(e => {
// Some criteria to check against for the form.
// Obviously you could make a crafty selector and avoid `if (e.hasClass())`,
// but this is to demonstrate using if to test the form if you wanted to check the form element in any way before applying `.validate()`.
if (e.hasClass("someClass") {
e.validate(...);
}
});

Ember.js - I want an Action event (on="") to trigger when there is a transition to a new Route

I want an Action event (on="") to trigger when there is a transition to a new Route.
I've seen the list of Action event handlers and closest I could find is attaching the action to the largest HTML element on the page and triggering it with 'Mousemove". This is a terribly flawed away of going about what I want to do.
So just to draw it out.
<div {{action 'displayEitherHtml1or2'}} class="largestDOMelement">
{{#if showHtml1}}
// html 1 inside
{{/if}}
{{#if showHtml2}}
// html 2 inside
{{/if}}
</div>
'/objects' is a list of objects and clicking one leads to 'object/somenumber'. The action should automatically trigger when I enter the 'object/somenumber' page.
UPDATE: I've taken the contents from the previous update and dumped them into my DocRoute, but nothing it being triggered when I transition to 'doc' through {{#link-to 'doc' this.docID}} {{docTitle}}{{/link-to}}
VpcYeoman.DocRoute = Ember.Route.extend(VpcYeoman.Authenticated,{
toggleLetterSwitch: false,
togglePermitSwitch: false,
activate: function () {
var docTemplateID = this.get('docTemplateID');
if ( docTemplateID == 2) {
this.set('toggleLetterSwitch', true);
this.set('togglePermitSwitch', false);
console.log('docTemplateID equals 2');
} else {
this.set('toggleLetterSwitch', false);
this.set('togglePermitSwitch', true);
}
}
});
UPDATE DOS: setDocID is set in the DocsController to 1. Here's the whole thing.
VpcYeoman.DocsController = Ember.ArrayController.extend({
tempDocId: 1,
actions: {
addDoc: function (params) {
var docTitle = this.get('docTitle');
var docTemplateID = 1;
var docTemplateID = this.get('tempDocId');
console.log(this.get('tempDocId'));
var store = this.store;
var current_object = this;
var doc = current_object.store.createRecord('doc', {
docTitle:docTitle,
docTemplateID:docTemplateID
});
doc.save();
return true;
},
setDocId: function (param) {
this.set('tempDocId', param);
console.log(this.get('tempDocId'))
},
}
});
As #fanta commented, it seems like you're looking for the activate hook within your Route. This gets called when you enter the route where you define it. If you want to call it on every transition, you might consider defining a base route for your application and extending that instead of Em.Route:
App.BaseRoute = Em.Route.extend(
activate: function () {
// Do your thing
}
);
App.YourRoutes = App.BaseRoute.extend()
It's possible that there's a more appropriate place/time to do this, but without knowing quite what your action does, this is probably the best guess.
ETA: Looking at your edit, you won't want all your routes to extend App.BaseRoute the way I did it above; you should probably just include that activate hook explicitly in the routes which need it.

Dynamically change the handler of a click binding in knockout.js

I got the following binding working like a charm :
<button class="flatButton buttonPin" data-bind="click:EnterPinMode">Add pin</button>
In my viewmodel I define the Handler like this :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
}
Now, let's say I want to change the behavior of that button after the first click on it...how could I do it ? I already managed quite easily to change the button text :
self.EnterPinMode = function(data,event)
{
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
}
But what about changing the button behaviour ? If I had set this handler through jQuery, that wouldn't be an issue, but is there a way to "replace" the click binding on that control so that now it will call ExitPinMode handler, for example.
I've got some doubts on this being possible given the fact that knockout works only with declarative binding (at least without plugin...), but I thought it was worth asking.
Please note that I will actually need some kind of 3 ways toggle, I just simplify it here to a "normal" toggle for the sake of the example.
I think using a hasBeenClicked flag that's private to the view model is fine, and probably the best solution for this.
If you really want to swap out the handler, that should be easy enough, though, with something like this:
function enterPinMode() {
//Doing several things here
//....
var curButton = $(event.target);
curButton.text("Cancel");
//set click handler to a step 2 function
self.pinAction = exitPinMode;
}
function exitPinMode() {
//....
}
self.pinAction = enterPinMode;
Maybe one of the simplest solution is to add a boolean like hasBeenClicked set to false at the begining and then set it to true.
Example:
self.hasBeenClicked = false;
self.EnterPinMode = function(data,event)
{
if (!self.hasBeenClicked )
{
var curButton = $(event.target);
curButton.text("Cancel");
self.hasBeenClicked = true;
}
else
{
//behaviour an a second click
}
}
Hope it helps !
You can try this
var vm = function () {
var self = this;
var nextState = 'firstState';
var states = {
firstState: function () {
nextState = 'secondState';
//Do stuff
},
secondState: function () {
nextState = 'thirdState';
//Do stuff
},
thirdState: function () {
nextState = 'firstState';
//Do stuff
}
}
self.EnterPinMode = function () {
states[nextState].call();
}
}
What you should try to remember first about MVVM is that you are designing an object to represent your view. If your view will have different states, There is nothing wrong with having your viewmodel know about these states and knowing what to do in what state. Stick with MVVM. You wont be disappointed.

Save data from a jQuery Accordion

I have a sortable accordion loaded with a foreach-template loop over a ko.observableArray() named "Tasks".
In the accordion I render the TaskId, the TaskName, and a task Description - all ko.observable().
TaskName and Description is rendered in input/textarea elements.
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
I need to match the TaskId with the Tasks-array to fetch the actual key/value-pair to send to the saveEdit().
This is the HTML:
<div id="accordion" data-bind="jqAccordion:{},template: {name: 'task-template',foreach: Tasks,afteradd: function(elem){$(elem).trigger('valueChanged');}}"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName /></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
This is the binding:
ko.bindingHandlers.jqAccordion = {
init: function(element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged",function(){
ko.bindingHandlers.jqAccordion.update(element,valueAccessor);
});
},
update: function(element,valueAccessor) {
var options = valueAccessor();
$(element).accordion('destroy').accordion(
{
// options put here....
header: "> div > h3"
, collapsible: true
, active: false
, heightStyle: "content"
})
.sortable({
axis: "y",
handle: "h3",
stop: function (event, ui) {
var items = [];
ui.item.siblings().andSelf().each(function () {
//compare data('index') and the real index
if ($(this).data('index') != $(this).index()) {
items.push(this.id);
}
});
// IE doesn't register the blur when sorting
// so trigger focusout handlers to remove .ui-state-focus
ui.item.children("h3").triggerHandler("focusout");
if (items.length) $("#sekvens3").text(items.join(','));
ui.item.parent().trigger('stop');
}
})
.on('stop', function () {
$(this).siblings().andSelf().each(function (i) {
$(this).data('index', i);
});
})
.trigger('stop');
};
};
My first thought was to place the line
$root.SelectedTask( ui.options.active );
in an .on('click') event function where SelectedTask is a ko.observable defined in my viewModel. However, the .on('click') event seems to be called a lot and it's generating a lot of traffic. Also, I can´t quite figure out where to put the save(item) call that sends the selected "item" from Tasks via an ajax-function to the database.
Any help is highly appreciated. Thanks in advance! :)
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
This sounds like the core of what you want to do. Let's start out with a Task model
function Task (data) {
var self = this;
data = data || {};
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
}
And then we need our View Model:
function ViewModel () {
var self = this;
self.tasks = ko.observableArray();
self.selectedTask = ko.observable();
self.saveTask = function (task) {
$.ajax({ ... });// ajax call that sends the changed data to the server
};
var taskSubscription = function (newValue) {
self.saveTask(self.selectedTask());
};
var nameSubscription, descriptionSubscription;
self.selectedTask.subscribe(function (newlySelectedTask) {
if (newlySelectedTask instanceof Task) {
nameSubscription =
newlySelectedTask.name.subscribe(taskSubscription);
descriptionSubscription =
newlySelectedTask.description.subscribe(taskSubscription);
self.saveTask(newlySelectedTask);// But why?
}
});
self.selectedTask.subscribe(function (currentlySelectedTask) {
if (currentlySelectedTask instanceof Task) {
nameSubscription.dispose();
descriptionSubscription.dispose();
self.saveTask(currentlySelectedTask);// But why?
}
}, null, 'beforeChange');
}
So what's going on here? Most of this should be pretty self explanatory so I'm just going to focus on the subscriptions. We created a taskSubscription function so we're not constantly having it defined every time the self.selectedTask changes.
We have two subscriber functions. The first fires after the selectedTask's value has changed and the second fires before it changes. In both, we verify that the new value is an instance of a Task object. In the after change subscription, we set up two subscriptions on the name and description properties. Then I capture the return value from the subscription function into two private variables. These are used in the before change function to dispose of those subscriptions so that if those Tasks are ever updated when they're not currently selected, then we don't continue to fire off the saveTask function.
I've also added self.saveTask in each of the subscriptions to the selectedTask observable. I asked why in here because, why save it if we don't know if the value has changed or not? You may be making ajax requests needlessly here.
Also, as demonstrated by this code, you can set up these subscriptions to make ajax requests every time the value changes but that may end up making a LOT of requests. A better option might be to set up functionality in your Task model that can track whether or not it is 'dirty' or not. Meaning one or more of its values have changed that requires updating.
function Task (data) {
var self = this;
// Make a copy of the data object coming in and use this to save previous values
self._data = data = $.extend(true, { id: null, name: null, description: null }, data);
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
for (var prop in data) {
if (ko.isSubscribable(self[prop])) {
self[prop].subscribe(function (oldValue) {
data[prop] = oldValue;
}, null, 'beforeChange');
}
}
}
Task.prototype.isDirty = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
if (self._data[prop] !== self[prop]())
return true;
}
}
return false;
};
And of course you need a way to save it, or make it not dirty
Task.prototype.save = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
self._data[prop] = self[prop]();
}
}
};
Using the same concept you can also create Task.prototype.revert that does the opposite of what .save does. With all this in place, you could forego setting up the subscriptions on the individual name and description properties. I wanted to show that option to just demonstrate how one might want to use the .dispose method on a subscription. But now you can just subscribe to the selectedTask observable ('beforeChange') and see if the currently selected task that you're about to swap out isDirty. If it is, call the saveTask function, and when that completes, call the .save function on the Task so that it is no longer dirty.
This is probably the route I would go in implementing something like this. The beauty of it is, I haven't written a single line of code that has anything to do with the manipulating the View. You can set the selectedTask any way you see fit. What I would do is, bind the selectedTask observable to a click binding on the <h3> element inside of the accordion. That way, every time a user clicks on any of the accordions, it will potentially save the previously selected task (if any of the property values had changed).
Hopefully that addresses your scenario here of trying to save a Task when certain events are triggered.

Knockout Validation and Qtip

I currently use Jquery Validation and Qtip together to deal with the actual validation and displaying of information to the screen using the nice tooltip style notifications upon validation errors using the errorPlacement component of the validation options.
Currently each viewModel has its own custom method for setting up and kicking off the validation and callbacks, however I was trying to look at a nicer way of doing this, be it adding a custom binding to setup my validation rules via the data-bindings or an alternative way, but still yielding the same results (i.e the errorPlacement is triggered when a validation error occurs and tells Qtip to display the error for the given element).
Now before I started making one myself I just checked online and found Knockout Validation, which I initially thought was a great idea, I could apply my validation logic directly to the data within my viewModel and then just find some sort of callback to get Qtip to kick in, however it seems there is no callback that I can find documented. The library seems to do everything I want for the validation side of things, just not for the displaying side of things. I looked through the source code and examples but couldn't see anything other than ko.validation.group(viewModel) which would give me an observable containing the errors, but I am not sure if I could use this the same way as I was expecting.
Here is an example of how my current validation happens:
/*globals $ ko */
function SomeViewModel() {
this.SetupValidation = function () {
var formValidationOptions = {
submitHandler: self.DoSomethingWhenValid,
success: $.noop,
errorPlacement: function (error, element) {
if (!error.is(':empty'))
{ qtip.DoSomethingToDisplayValidationErrorForElement(element, error); }
else
{ qtip.DoSomethingToHideValidationErrorForElement(element); }
}
};
$(someForm).validate(formValidationOptions);
this.SetupValidationRules();
};
this.SetupValidationRules = function() {
$(someFormElement1).rules("add", { required: true, minlength: 6, maxlength: 20, alphaNumeric: true });
$(someFormElement2).rules("add", { required: true, minlength: 6, maxlength: 20 });
$(someFormElement3).rules("add", { required: true, email: true, });
};
}
I currently am sure I can remove the need for the validation rules method by adding a custom binding so I can set the validation in the data-bind, however if possible I would like to use the same sort of callback approach with the existing Knockout-Validation binding.
I haven't used Knockout-Validation specifically but I have written something similar in the past. A quick glance at the source shows that each extended observable gets a sub-observable isValid. This could be used to hide show messages in your markup using conventional knockout visible bindings.
To get QTip to work a custom binding could subscribe to this isValid property and perform the necessary initialization to show/hide QTip when triggered.
EDIT
Here is an example to get you started
http://jsfiddle.net/madcapnmckay/hfcj7/
HTML:
<!-- Note that you have to reference the "qtipValMessage" binding -->
<!-- using the "value" binding alone is not enough -->
<input data-bind="value: emailAddress, qtipValMessage : emailAddress" />
JS:
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
observable.isValid.subscribe(function(valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
});
}
}
};
I had been editing madcapnmckay's post, but the differences have become significant enough that I think a new answer is needed.
It is heavily based off of madcapnmckay's post, but it fixes a bug pointed out by MorganTiley. The original only works if the user has modified the observable. If they haven't then the code never gets fired. So, I've modified it so that it fires the tooltip code when it gets created, in addition to when it changes.
ko.bindingHandlers.qtipValMessage = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.qtip({
overwrite: true,
content: {
text: observable.error
}
});
} else {
$element.qtip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};
The one downside is that the tooltip will display on hover before knockout validation has been run (example, you have a "required" validation on a field, before you press submit a tooltip will display saying the field is required, but the field will not highlight in pink). Once you change the field however, the tooltip will disappear if the field is valid.
My app was not using qtip, but rather Twitter Bootstrap Tooltip, so here is the code for that as well.
ko.bindingHandlers.invalidTooltip = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor(), $element = $(element);
if (observable.isValid) {
var updateTooltip = function (valid) {
if (!valid) {
$element.attr("data-original-title", observable.error);
$element.tooltip();
} else {
$element.tooltip("destroy");
}
}
updateTooltip();
observable.isValid.subscribe(updateTooltip);
}
}
};

Categories

Resources