For a quick view of my problem I have made a working jsFiddle here:
In KnockoutJS I have made a custom extender validator to test if the input format is in the HHMM format. If it is it returns the new value, if it doesn't it will set it back to the old value this is currently working.
ko.extenders.acValidTimeHHMM = function (target, options) {
var result = ko.computed({
read: target,
write: function (newValue) {
var re = /^([0-9]|0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$/;
if (!re.test(newValue)) {
target.notifySubscribers(target());
//Time not in correct format return old time
return;
}
target(newValue);
}
}).extend({ notify: 'always' });
result(target());
return result;
};
The problem I am having is that I update my database when the value changes using a computed. However this is also firing when I reset the value back to its original using my validator. (Method based on Ryan Rahlf dirty flag technique here )
self.update = ko.computed(function () {
self.timeOne();
self.timeTwo();
alert("Fired");
});
The problem is obviously the line target.notifySubscribers(target()); in my validator. However without this line I can't reset the value to its old value and I can't find another way to do this.
So this only fires when a value actually changes rather then the validator resetting it. The jsFiddle demonstrates my problem exactly and can be used to make a working version (hopefully) I know its currently firing on page load too.
The problem I am having is that I update my database when the value changes using a computed.
I don't know all your logic, but I don't think this is a good idea to update the db each time your knockout view model has updated. May be you should look at knockout validation plugin. Using this plugin you can build the same custom validation rule and update the db only on form submit event.
About your problem...
The simplest solution I'm found is to send a success callback function to the validation extension like an option.
Something like this.
JS:
var ViewModel = function() {
var update = function () {
alert("value was successfully changed");
};
var cancel = function () {
alert("validation failed. previous value was returned");
};
var timeOne = ko.observable("1100").
extend({
acValidTimeHHMM: {
success: update,
fail: cancel
}
});
var timeTwo = ko.observable("1248").
extend({ acValidTimeHHMM: { success: update } });
return {
timeOne: timeOne,
timeTwo: timeTwo
};
};
ko.extenders.acValidTimeHHMM = function(target, option) {
var baseOptions = {
success: null,
fail: null
};
$.extend(baseOptions, option);
var result = ko.computed({
read: target,
write: function (newValue) {
var oldValue = target();
if(newValue == oldValue) return;
var re = /^([0-9]|0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$/;
if (!re.test(newValue)) {
target.notifySubscribers(oldValue);
if(typeof(baseOptions.fail) == "function")
baseOptions.fail();
return;
}
target(newValue);
if(typeof(baseOptions.success) == "function")
baseOptions.success()
}
}).extend({ notify: 'always' });
result(target());
return result;
};
ko.applyBindings(new ViewModel());
HTML:
<p>Time One<input data-bind='value: timeOne' /></p>
<p>Time Two<input data-bind='value: timeTwo' /></p>
Related
I wanted to give some form visual validation cues, so I tried using class binding to do just that. If I use the ternary inline, it doesn't really meet my requirements of what should happen, but when I tried using computed prop, it made all the other components disappear.
If i tried doing it like this: v-bind:class="[(!validation.hasError('form.fullName'))?'has-success':'has-danger noValid']"
It just triggers the animation and the classes once and they stays there. I want to trigger the noValid animation everytime the user clicks my submit button if there's errors in validation.
I'm using simple-vue-validator btw.
Here's the godforsaken component, I use vue now-ui-kit template from Creative Tim as a base and customize my way from there. This is their Form Group Input component, docs here
<fg-input
class="input-lg"
:label="validation.firstError('form.fullName')"
placeholder="Full Name..."
v-model="form.fullName"
addon-left-icon="now-ui-icons users_circle-08"
v-bind:class="{ visualValidation }"
></fg-input>
Button Component: bootstrap-vue, cause their customized button doesn't really serve my purpose
<b-button type="submit" block pill variant="info" #click="submit">Submit Form</b-button>
My computation: noValid is the shaking animation class, has-success and has-danger just changes their appearances.
computed: {
visualValidation: function() {
const successClass = "has-success";
const errorBoi = "has-danger";
const shakeBoi = "noValid";
if (validation.firstError("form.fullName")) {
return errorBoi + " " + shakeBoi;
} else if (!validation.hasError("form.fullName")) {
return successClass;
}
}
}
I thought the variables that i returned would be binded as classes to the form.fullName Model but it's not doing anything, better yet, it made all my other components not rendering.
What should i do here? i'm just starting to learn Vue.js so i don't really understand what's going on here.
Edit from here:
I rewrote the logic to my class binding, and just use method to remove the class on click the components.
here is the updated component:
<fg-input
class="input-lg"
:label="validation.firstError('form.email')"
placeholder="Email Here..."
v-model="form.email"
addon-left-icon="now-ui-icons ui-1_email-85"
v-bind:class=" {'has-success' : isSuccEmail, 'has-danger' : isFailEmail, 'noValid': validNoEmail} "
#click="removeShake()"
></fg-input>
data:
isSuccEmail: false,
isFailEmail: false,
validNoEmail: false
method:
removeShake: function() {
let vm = this;
vm.validNoName = false;
vm.validNoEmail = false;
console.log(vm.validNoEmail);
console.log(vm.validNoName);
},
However, there's currently a bug, where the validator don't validate separately. It gave the class has-success to email even though it was full-name that was successful.
valEmail: function() {
let vm = this;
vm.$validate("form.email").then(function(success) {
if (success) {
vm.isFailEmail = false;
vm.isSuccEmail = true;
} else if (!success) {
vm.isSuccEmail = false;
vm.isFailEmail = true;
vm.validNoEmail = true;
} else {
alert("Fatal Error");
}
});
},
valName: function() {
let vm = this;
vm.$validate("form.fullName").then(function(success) {
if (success) {
vm.isFailName = false;
vm.isSuccName = true;
} else if (!success) {
vm.isSuccName = false;
vm.isFailName = true;
vm.validNoName = true;
console.log(vm);
} else {
alert("Fatal Error");
}
});
}
The $validate is a function of simple-vue-validator, the docs.
Submit function is just calling those two functions above.
I think this because of the promise call, is there a way to call the $validate() without promise?
There are a few problems here.
Firstly, while templates don't require you to use this. you still need to use it within your JS code. You should be seeing an error in the console, or maybe even at compile time depending on how you have linting configured.
if (validation.firstError("form.fullName")) {
What is validation? I assume that should be this.validation. Likewise a couple of lines further down.
Your next problem is here:
v-bind:class="{ visualValidation }"
The braces here are creating an object literal, so it's equivalent to this:
v-bind:class="{ visualValidation: visualValidation }"
This will be conditionally adding the string 'visualValidation' as a class , which isn't what you want. Get rid of the braces:
v-bind:class="visualValidation"
This has been asked and answered several times, yet I keep comparing my syntax to the proper syntax as described in the answeres, and can't find what's wrong.
$('.voteBtn').bind('click', function () {
var vote = 1;
$.post(
"vote.php",
{ vote: vote},
function(vote) {
alert (vote); // BUG HERE
}
);
});
My expected result is an alert of '1', as defined at var vote = 1;
But the current result is an alert of a blank string.
What am I doing wrong?
you have a parameter called vote which is causing the problem, so inside the ajax callback vote variable is referring to the data returned by the ajax request, not the closure variable in the click handler
To fix the problem rename the parameter to something else like data
$('.voteBtn').bind('click', function () {
var vote = 1;
$.post("vote.php", {
vote: vote,
pollID: id
}, function (data) {
alert(vote); // BUG HERE
});
});
Currently, vote that you alert is the data returned from your ajax request, not the one you defined before, try to use this instead:
$('.voteBtn').bind('click', function () {
var $vote = 1;
$.post(
"vote.php",
{ vote: $vote},
function(vote) {
alert ($vote); // BUG HERE
}
);
});
I have a page that is built around a wrapper with some very defined logic. There is a Save button on the bottom of the wrapped form that looks like this:
<form>
... my page goes here...
<input id="submitBtnSaveId" type="button" onclick="submitPage('save', 'auto', event)" value="Save">
</form>
This cannot change...
Now, I'm writing some javascript into the page that gets loaded in "...my page goes here...". The code loads great and runs as expected. It does some work around the form elements and I've even injected some on-page validation. This is where I'm stuck. I'm trying to "intercept" the onclick and stop the page from calling "submitPage()" if the validation fails. I'm using prototype.js, so I've tried all variations and combinations like this:
document.observe("dom:loaded", function() {
Element.observe('submitBtnSaveId', 'click', function (e) {
console.log('Noticed a submit taking place... please make it stop!');
//validateForm(e);
Event.stop(e);
e.stopPropagation();
e.cancelBubble = true;
console.log(e);
alert('Stop the default submit!');
return false;
}, false);
});
Nothing stops the "submitPage()" from being called! The observe actually works and triggers the console message and shows the alert for a second. Then the "submitPage()" kicks in and everything goes bye-bye. I've removed the onclick attached to the button in Firebug, and my validation and alert all work as intended, so it leads me to think that the propagation isn't really being stopped for the onclick?
What am I missing?
So based on the fact that you can't change the HTML - here's an idea.
leave your current javascript as is to catch the click event - but add this to the dom:loaded event
$('submitBtnSaveId').writeAttribute('onclick',null);
this will remove the onclick attribute so hopefully the event wont be called
so your javascript will look like this
document.observe("dom:loaded", function() {
$('submitBtnSaveId').writeAttribute('onclick',null);
Element.observe('submitBtnSaveId', 'click', function (e) {
console.log('Noticed a submit taking place... please make it stop!');
//validateForm(e);
Event.stop(e);
e.stopPropagation();
e.cancelBubble = true;
console.log(e);
alert('Stop the default submit!');
return false;
submitPage('save', 'auto', e);
//run submitPage() if all is good
}, false);
});
I took the idea presented by Geek Num 88 and extended it to fully meet my need. I didn't know about the ability to overwrite the attribute, which was great! The problem continued to be that I needed to run submitPage() if all is good, and that method's parameters and call could be different per page. That ended up being trickier than just a simple call on success. Here's my final code:
document.observe("dom:loaded", function() {
var allButtons = $$('input[type=button]');
allButtons.each(function (oneButton) {
if (oneButton.value === 'Save') {
var originalSubmit = oneButton.readAttribute('onclick');
var originalMethod = getMethodName(originalSubmit);
var originalParameters = getMethodParameters(originalSubmit);
oneButton.writeAttribute('onclick', null);
Element.observe(oneButton, 'click', function (e) {
if (validateForm(e)) {
return window[originalMethod].apply(this, originalParameters || []);
}
}, false);
}
});
});
function getMethodName(theMethod) {
return theMethod.substring(0, theMethod.indexOf('('))
}
function getMethodParameters(theMethod) {
var parameterCommaDelimited = theMethod.substring(theMethod.indexOf('(') + 1, theMethod.indexOf(')'));
var parameterArray = parameterCommaDelimited.split(",");
var finalParamArray = [];
parameterArray.forEach(function(oneParam) {
finalParamArray.push(oneParam.trim().replace("'","", 'g'));
});
return finalParamArray;
}
The Event object in jQuery has this helpful preventDefault() method that prevents the default behaviour, obviously.
This is usually used to prevent click events from performing the browser default behaviour.
It seems like it would also be useful for custom events as well.
The task I'd like to achieve with this behaviour is a separate concern but I will explain it as an example for the behaviour I'm looking for:
I have a simple plugin that creates a popup out of a div. I found it on the internet.
$(selector).pop();
I have hacked it to close when you click on anything but a child of the popup, and to prevent default click behaviour on the clicked element.
function closeInactivePop() {
var foundAny = false;
jQ.each(function (i) {
var $this = $(this);
if ($this.hasClass('active') && ! $this.data('activePop')) {
$this.removeClass('active');
foundAny = true;
}
});
return foundAny;
}
$('body').click(function(){
// If we closed any, cancel the propagation. Otherwise let it be.
if (closeInactivePop()) {
$(document).trigger('jQuery.pop.menuClosed');
return false;
}
});
(Now that I paste it I realise I could have done this a bit better, but that notwithstanding).
Now I have added a new plugin that draws a colour picker inside the popup. Except the DOM that this colour picker creates is not inside the popup; it is only inside it visually. The DOM structure is separate.
In the aforementioned hack I would prefer to in fact fire another event, one whose default behaviour is to close the popup.
function closeInactivePop() {
var foundAny = false;
jQ.each(function (i) {
var $this = $(this);
if ($this.hasClass('active') && ! $this.data('activePop')) {
$(document).trigger('jQuery.pop.menuClosed');
$this.removeClass('active');
foundAny = true;
}
});
return foundAny;
}
$('*').click(function(e) {
var $this = $(this);
// This bit is pseudocode, where the Function is the default behaviour
// for this event.
// It is helpful that $this is actually the clicked element and not the body.
$this.trigger('jQuery.pop.menuBeforeClose', function() {
// if we run default behaviour, try to close the popup, or re-trigger the click.
if (!closeInactivePop()) {
$this.trigger(e);
}
});
});
Then I could later do
$('#colour-picker').bind('jQuery.pop.menuBeforeClose', function(e) {
e.preventDefault();
});
And this would prevent the closeInactivePopup default behaviour running when the target of the original click event was the colour picker or something inside it.
Can I do this somehow, even hackily?
I doubt that there is a native way to do that. However, you can either use "triggerHandler()" instead of "trigger()", which provides the ability to return values from the event handlers. Another relatively simple solution is to pass a custom "event" object that can be used to cancel the planned action:
function Action() {
var status = true;
this.cancel = function() { status = false; };
this.status = function() { return status; };
}
$('button').click(function() {
var action = new Action();
$(this).trigger('foo', [action]);
if (action.status()) {
// ... perform default action
}
});
In the event handler:
$('*').bind('foo', function(event, action) {
action.cancel();
});
Preface: I am sure this is incredibly simple, but I have searched this site & the jQuery site and can't figure out the right search term to get an answer - please excuse my ignorance!
I am adding additional form fields using jQuery's ajax function and need to then apply additional ajax functions to those fields but can't seem to get jQuery to monitor these on the fly form fields.
How can I get jQuery to use these new fields?
$(document).ready(function() {
$('#formField').hide();
$('.lnk').click(function() {
var t = this.id;
$('#formField').show(400);
$('#form').load('loader.php?val=' + t);
});
//This works fine if the field is already present
var name = $('#name');
var email = $('#email');
$('#uid').keyup(function () {
var t = this;
if (this.value != this.lastValue) {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(function () {
$.ajax({
url: 'loader.php',
data: 'action=getUser&uid=' + t.value,
type: 'get',
success: function (j) {
va = j.split("|");
displayname = va[1];
mail = va[2];
name.val(displayname);
email.val(mail);
}
});
}, 200);
this.lastValue = this.value;
}
});
});
So if the is present in the basic html page the function works, but if it arrives by the $.load function it doesn't - presumably because $(document).ready has already started.
I did try:
$(document).ready(function() {
$('#formField').hide();
$('.lnk').click(function() {
var t = this.id;
$('#formField').show(400);
$('#form').load('loader.php?val=' + t);
prepUid();
});
});
function prepUid(){
var name = $('#name');
var email = $('#email');
$('#uid').keyup(function () {
snip...........
But it didn't seem to work...
I think you are close. You need to add your keyup handler once the .load call is complete. Try changing this...
$('#form').load('loader.php?val=' + t);
prepUid();
To this...
$('#form').load('loader.php?val=' + t, null, prepUid);
What you are looking for is the jquery live function.
Attach a handler to the event for all elements which match the current selector, now or in the future
You can do something like this:
$('.clickme').live('click', function() {// Live handler called.});
and then add something using the DOM
$('body').append('<div class="clickme">Another target</div>');
When you click the div added above it will trigger the click handler as you expect with statically loaded dom nodes.
You can read more here: http://api.jquery.com/live/