Knockout Validation and Qtip - javascript

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);
}
}
};

Related

Show JqueryUI tooltip based on parsley validation

I am having some trouble with getting the JQueryUI Tooltip Widget working with parsley validation. This is my code:
$.listen('parsley:field:error', function (fieldInstance) {
var messages = ParsleyUI.getErrorsMessages(fieldInstance);
if(fieldInstance.$element.tooltip('instance') != undefined) {
fieldInstance.$element.tooltip('destroy');
}
fieldInstance.$element.tooltip({
items: fieldInstance.$element,
content: messages,
show: 'pulsate'
});
fieldInstance.$element.tooltip('show');
});
My methology is:
Check if a tooltip exists (as multiple validation occur), if it does destroy it.
Create the tooltip with the appropriate message
Show the tooltip
But I just get a consol error:
Uncaught Error: no such method 'show' for tooltip widget instance
Also, if anyone thinks there is a better way of doing this please don't hesitate to answer!
You have a few issues with your code:
The main issue is that you're calling .tooltip('show'); but there is no such method or event, according to the API documentation. You have to use .tooltip('open').
The content option accepts a function or string and you're passing an array. You need to implode the messages array with something like messages.join('<br />')
In order to show the errors only within the tooltip, you need to change the default options of parlsey, specifically errorsContainer and errorsWrapper.
Your final code will be something like this (test in this jsfiddle):
$(document).ready(function() {
$("#myForm").parsley({
errorsContainer: function (ParsleyField) {
return ParsleyField.$element.attr("title");
},
errorsWrapper: false
});
$.listen('parsley:field:error', function (fieldInstance) {
var messages = ParsleyUI.getErrorsMessages(fieldInstance);
if(fieldInstance.$element.tooltip('instance') != undefined) {
fieldInstance.$element.tooltip('destroy');
}
fieldInstance.$element.tooltip({
content: messages.join('<br />'),
items: fieldInstance.$element,
show: 'pulsate'
});
fieldInstance.$element.tooltip('open');
});
});

Knockout binding and CK Editor toolbar not appearing

I am totally new to knock-out custom binding, I am trying to integrate ckeditor with knock-out biding, I have the following binding got from Google search,
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('contenteditable', true);
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
$element.on('input, change, keyup, mouseup', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
I have the following code to bind,
$(function () {
$.getJSON("/getdata", function (data) {
ko.applyBindings({
testList: [{
test: ko.observable()
},
{
test: ko.observable()
}]
}, document.getElementById('htmled'));
});
});
HTML as follows
<div id="htmled" data-bind="foreach:testList">
Data
<div class="editor" data-bind="wysiwyg: test">Edit this data</div>
</div>
The binding works and show the toolbar when I call the ko.applyBindings outside the $.getJSON method. But when I call applyBindings inside, the toolbars not appearing. Can any body help me on this? I must be missing something for sure, any help on this is greatly appreciated.
Jsfiddle Added
Working :http://jsfiddle.net/jogejyothish/h4Lt3/1/
Not Working : http://jsfiddle.net/jogejyothish/Se8yR/2/
Jyothish
What's happening is this:
Your page loads with the single div. KO has yet to be applied to this div.
document.ready() fires. The CKEditor script applied CKEditor to any matching divs (none).
You make your ajax call.
The Ajax call completes. You apply bindings.
KO inserts two new divs, neither of which has CKEditor.
In order to fix it, you need to add some code inside your ajax success function to manually initialise the CKEditors, like:
$(".editor").each(function(idx, el) {
CKEDITOR.inline(el)
});
Here it is, working in your fiddle:
http://jsfiddle.net/Se8yR/5/
The reason your working version works is because the bindings are applied in document.ready, so KO renders the two div elements in time, and the CKEditor is successfully applied to them.
CKEditor takes some time to load.
In your first example, it loads after ko applies, which works fine.
In the second example, it loads before ko applies. The problem is that CKEditor looks for the contenteditable attribute which you set with ko, so the editor is not created.
You can create it manually with:
CKEDITOR.inline(element).setData(valueUnwrapped || $element.html());
Doc
Demo

Parsley.js issues, adding class to parent of form field

I've seen this question a few times, and from what I can gather, either of these two options should work. However I cannot get errorsContainer to work. My goal is to add the error and valid classes to the direct parent of the form field. Extra kudos if if you can tell me how to disable the appending of the errors list while still adding these validation classes.
var parsleyConfig = {
errorClass: 'error',
successClass: 'valid',
errors: {
errorsContainer: function(el) {
return el.$element.parent();
//return $(el).closest('.parent');
}
}
}
var parsleyConfig = {
errorClass: 'error',
successClass: 'valid',
errorsContainer: function(el) {
return el.$element.parent();
//return $(el).closest('.parent');
}
}
$('#registerForm').parsley(parsleyConfig);
In both cases, the class is added to the form field itself, not its parent. Result is the same with data-parsley-validate included or not in the form element. Also running the latest 2.0 thanks!!
You have to use the classHandler option instead of errorsContainer
var parsleyOptions = {
errorClass: 'error',
successClass: 'valid',
errorsMessagesDisabled: true,
classHandler: function(el) {
return el.$element.parent();
}
};
$formSelector.parsley(parsleyOptions);
errorsContainer return the $element where errors messages will be appended as described in annotated source defaults options.
errorsMessagesDisabled prevent Parsley from adding errors list.

TinyMCE with knockout data binding would´t update

I have the TinyMCE WYSiWYG Editor presenting text depending on a selected object, but experience problem with the binding.
The first "instanciation" seems to work, but when choosing a new text from the drop down list of available text the editor goes blank and Firebug console tells me:
TypeError: D.hasChildNodes is not a function
...ute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r...
and
NS_ERROR_UNEXPECTED: Unexpected error
.../,"$1"));return false}});if(!u.getParam("accessibility_focus")){g.add(i.add(k,"a...
I have tried to recreate my code here: http://jsfiddle.net/xc4sz/1/
It´s not 100% but at least it does´t work. ;)
If I instead of clicking directly from text 1 to text 2 go via the "Choose option" the text is presented properly.
I guess it has to do with the "init" section below:
ko.bindingHandlers.tinymce = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().tinymceOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
var el = $(element)
//handle edits made in the editor. Updates after an undo point is reached.
options.setup = function (ed) {
console.log(ed)
ed.onChange.add(function (ed, l) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(l.content);
}
});
};
//handle destroying an editor
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
//$(element).tinymce(options);
setTimeout(function () { $(element).tinymce(options); }, 0);
el.html(value);
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
var $element = $(element),
value = ko.utils.unwrapObservable(valueAccessor()),
id = $element.attr('id');
//handle programmatic updates to the observable
// also makes sure it doesn't update it if it's the same.
// otherwise, it will reload the instance, causing the cursor to jump.
if (id !== undefined) {
var tinymceInstance = tinyMCE.get(id);
if (!tinymceInstance)
return;
var content = tinymceInstance.getContent({ format: 'raw' });
if (content !== value) {
$element.val(value);
//this should be more proper but ctr+c, ctr+v is broken, above need fixing
//tinymceInstance.setContent(value,{ format: 'raw' })
}
}
}
};
Depending on the versions of TinyMCE and jQuery that you are dependent on, you might like to try the custom binding I've recently rolled myself.
It's available on GitHub and NuGet
I found the issue. What happened was this:
you select Textbatch #1 and make some changes
you switch to Textbatch #2
the binding changes correctly from Textbatch #1 to #2
THEN the ed.onChange.add event handler kicks in and overwrites the content of the previous Textbatch #1 with that of the new Textbatch #2
Take a look at this updated fiddle (remove /show/light from the URL to get back to the editor). I had to inline select2.js, because Github does not allow files it hosts to be included remotely, causing your fiddle to fail.
The important part is in ko.utils.domNodeDisposal.addDisposeCallback:
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).tinymce().onChange.remove(changeHandler);
setTimeout(function () { $(element).tinymce().remove() }, 0)
});
I do not know why the removal of the editor is minimally delayed with a 0-second timeout, but there is probably a good reason for that. So all we do is remove the 'change' handler, so that the old editor cannot update the bound valueAccessor in your viewmodel anymore.
EDIT: I just noticed that I fixed your fiddle, but not necessarily your original exception… here is to hoping that the two were related.

Knockout validation beyond message display

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.

Categories

Resources