I'm trying to use jQuery to build a home-made validator. I think I found a limitation in jQuery: When assigning a jQuery value to a json variable, then using jQuery to add more DOM elements to the current page that fit the variable's query, there doesn't seem to be a way to access those DOM elements added to the page which fit the json variable's query.
Please consider the following code:
var add_form = {
$name_label: $("#add-form Label[for='Name']"),
$name: $("#add-form #Name"),
$description_label: $("#add-form Label[for='Description']"),
$description: $("#add-form #Description"),
$submit_button: $("#add-form input#Add"),
$errors: $("#add-form .error"),
error_marker: "<span class='error'> *</span>"
}
function ValidateForm() {
var isValid = true;
add_form.$errors.remove();
if (add_form.$name.val().length < 1 ) {
add_form.$name_label.after(add_form.error_marker);
isValid = false;
}
if (add_form.$description.val().length < 1) {
add_form.$description_label.after(add_form.error_marker);
isValid = false;
}
return isValid
}
$(function(){
add_form.$submit_button.live("click", function(e){
e.preventDefault();
if(ValidateForm())
{
//ajax form submission...
}
});
})
An example is availible here: http://jsfiddle.net/Macxj/3/
First, I make a json variable to represent the html add form. Then, I make a function to validate the form. Last, I bind the click event of the form's submit button to validating the form.
Notice that I'm using the jQuery after() method to put a span containing an '*' after every invalid field label in the form. Also notice that I'm clearing the asterisks of the previous submission attempt from the form before re-validating it (this is what fails).
Apparently, the call to add_form.$errors.remove(); doesn't work because the $errors variable only points to the DOM elements that matched its query when it was created. At that point in time, none of the labels were suffixed with error_marker variable.
Thus, the jQuery variable doesn't recognize the matching elements of it's query when trying to remove them because they didn't exist when the variable was first assigned. It would be nice if a jQuery variable HAD AN eval() METHOD that would re-evaluate its containing query to see if any new DOM elements matched it. But alas...
Any suggestions?
Thanks in advance!
You are correct that a jQuery object is not "live" – that is, the set of elements in the jQuery object is not dynamically updated. (This is a good thing.)
If you really want to update an arbitrary jQuery object, you can get the selector used to create the object from .selector:
var els = $('#form input');
els.selector // '#form input'
So you could do els = $(els.selector); to re-query the DOM.
Note, however, that if you modified the collection after the initial selector (functions like add, filter, children, etc.), or if the jQuery object was created without using a selector (by passing a DOMElement), then .selector will be pretty much useless, since the selector will be empty, incorrect, or even potentially invalid.
Better is to re-structure your code in such a way that you aren't holding on to a stale jQuery object; the other answers make some good suggestions.
Also, please make sure you're validating input server-side too!
For the objects that are going to be changing, instead of making the JSON object reference a static value, make it a function:
$errors: function() { return $("#add-form .error"); },
since it's a function, it will re-evaluate the error fields every time you call add_form.$errors().
Your approach to the problem has got many structural problems:
Avoid using global variables
There may not always be just one form on the page that you are validating. What if one day you will decide that you will have several forms. Your add_form variable is a global variable and therefore would be conflicting.
Do not use the submit button click event for detecting form submissions.
What if a form is submitted by a js call like $("form").submit(); or by the enter key?
Store the selectors instead of the DOM objects if you are not certain that the objects already exist at the creation time of the configuration object.
.live is deprecated. Use .on instead.
It is 3. that will solve your actual problem, but I strongly recommend addressing all 4 issues.
For 2, the best place to attach the validator is not on the submit button, but on submit event of the form. Something like this:
$("#add-form").submit(function(event) {
event.preventDefault();
if (validateForm(this))
$.ajax({
url: $(this).attr("action"),
data: $(this).serialize(),
//ETC
});
});
Note how the form is now also much easier to access. Your configuration object no longer needs to store a reference to the submit button.
Your configuration object could now be simplified to be something like this:
{
name_label: "Label[for='Name']",
name: "#Name",
description_label: "Label[for='Description']",
description: "#Description",
errors: ".error",
error_marker: "<span class='error'> *</span>"
}
Within validateForm, you can use these selector as follows:
var $name_label = $(configuration.name_label, this); //Finds the label within the current form.
Now, to allow different configuration parameters for each form use something like this:
function enableValidation(form, configuration) {
$.extend(configuration, {
//Default configuration parameters here.
});
function validateForm(form) {
//Your original function here with modifications.
}
$(form).submit(funciton(e) {
if (!validateForm(this))
e.preventDefault();
});
}
function enableAjax(form) {
$(form).submit(function(e){
if (!e.isDefaultPrevented()) {
e.preventDefault();
$.ajax(...);
}
});
}
$(function() {
enableValidation("#add-form", {/*specialized config parameters here*/});
enableAjax("#add-form");
});
Related
I use jqInlineEdit for inline editing on a web page. Everything works, except I don't know how to get the id of the item which I need for saving the change to the database(via Django).
The HTML looks like this:
<div id="remark14756" class="remark" data-cid="14756">
Sample Text
</div>
That's the JavaScript:
<script src="/static/inline-edit.jquery.js"></script>
<script>
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (e, text, html) {
// Executes when exiting inline edit mode and a change has been made
c_id = $(this).attr("data-cid");
alert("Test: ", c_id)
}
});
</script>
Obviously, $(this) does not work in this context. I tried everything and searched a lot but I can't find how to do it the right way. Does anybody know the answer?
The inlineEdit docs say:
onChange(this, text, html) - Executes when exiting inline edit mode and a change has been made
with the use of this being quite misleading.
therefore the first param is actually the Element.
$(".remark").inlineEdit({
type: 'textarea',
onChange: function (elem, text, html) {
// `this` refers to inlineEdit instance plugin
// `elem` is the currently edited element
const c_id = $(elem).attr("data-cid");
alert(c_id); // 14756
}
});
That plugin is not performing in an expected "jQuery Plugin" way.
Usually properly written plugins should:
bind all methods to the Element callee,
(in case of Event methods) the first parameter should always refer to the original Event.
allowing a developer to reference it using the this keyword to get the native JS Element or either doing $(this) inside the exposed public Methods just like we're expected from native jQuery Methods, and to have accessible the Event (i.e: useful in case we use arrow functions to extract the currentTarget since the inexistence of this keyword)
$someElem.on('click', function(evt) {
const $el = $(this); // what we're used to
});
$someElem.on('click', (evt) => {
const $el = $(evt.currentTarget); // the importance of always passing the Event as first param
});
clearly not implemented in that plugin.
I'm trying to write a plugin that will select multiple elements and then apply some private methods to them (see code below). Then I also want to give the user the ability to trigger the activation of the plugin's methods manually with a .activate() function.
Here is my code :
MARKUP : https://github.com/simonwalsh/jquery.imagepox/blob/master/demo/index.html
JS : https://github.com/simonwalsh/jquery.imagepox/blob/master/dist/jquery.imagepox.js
Basically, when I select multiple items and then try to use the manual activation like so :
$(".pox-wrapper").imagepox({ // NOTE: selects two elements
manualActivation: true
});
var manual = $(".pox-wrapper").data('imagepox');
setTimeout(function(){
manual.activate();
}, 5000);
It will only apply the activate() method to the first element in the query...
This is my first jQuery plugin and I've been able to handle everything so far but I'm not sure about this one or even if it is the right way to effectively call a public method. I also tried using a custom event with an event listener in the plugin but it still only applies the methods on the first element in the page.
Thanks in advance :)
its not your plugin's fault. data does not work like that, it doesnt know how to return data from a collection of elements. Because think about it, each element in the collection contains its own data object!
So when you call data on a collection, it returns the data from the first one. The quick solution would be to change the innards of the setTimeout into a loop over all the elements in the set and call activate on them.
setTimeout(function(){
$(".pox-wrapper").each(function(){
$(this).data('imagepox').activate();
})
}, 5000);
It seems to me that you want to add functions to collections of jquery objects. This is the usecase of a jquery plugin. You can create a lightweight one like this:
$.fn.imagepox.activate = function(){ //do this after you create your plugin!
return this.each(function(){
var $this = $(this);
var data = $this.data('imagepox');
if(data){
data.activate();
}
});
};
now you can call it like this:
$(".pox-wrapper").imagepox.activate()
I am a rookie in JS, have a problem understanding JQUERY semantics.
I have a function written for checking the content of a cell.
Problem: the function just starts when the cell loses focus, if I click Submit, the error shows first, then it will run the function.
I want the function to run even when I am inside the cell. How to do it?
Initiated by this:
$(".user_id").blur(function(){ validateUserId($('.user_id')); });
The function:
function validateUserId(reference) {
if ( 5 == $(reference).val().length ) {
$.get('index.php?user=' + $(reference).val(), function(data) {
if ( "error" == data ) {
$(reference).parent().parent().addClass('error');
alert('error');
} else {
$(reference).parent().parent().removeClass('error');
$(reference).addClass('valid');
$(reference).parent().parent().addClass('success');
}
});
} else {
alert('short');
$(reference).parent().parent().addClass('error');
}
}
$(".user_id").on('keyup', function(){
validateUserId($(this));
});
i would use the keyup event.
So everytime somebody types a key in your input cell, the function will be executed.
$(".user_id").on("keyup", function(){ validateUserId($(this)); });
I changed the $(".user_id"), you take the value from ,to $(this). Since you want the value of the field you did the keyup event on. (And not an other field, if there would be 2 fields with the same .user_id class.)
Try to bind function on focus event
$("input[submit]").click(function(e){
e.preventDefault();
var valid = validateUserId($('.user_id')); // maybe the function could return a boolean value too instead of just adding classes to the HTML part
if (valid) {
$("#your_form_id").submit(); // only if the input is valid, submit it
}
});
sidenote on your problem: if you click the submit button it will first trigger the submit action which may give you an error and then execute the blur() block of code
Here is a working demo http://jsfiddle.net/pomeh/GwswM/. I've rewritten a little the code to:
cache DOM selections into variables,
use jQuery methods chaining,
improve if conditions with tripe equal signs,
separated AJAX calls and application logic,
cache identical AJAX calls,
use jQuery deferred to hide the asynchronous aspect of the verification (linked to AJAX)
Hope that'll help :)
I have several jQuery functions on my website but not on every page all of them are required.
All of them are exectued from one XXX.js file for example:
jQuery(function() {
$(".title").slug({
slug:'slug',
hide: false
}); });
and if any of the elements is missing the rest of the functions are not executed.
How to execute for example this function only if the element exists?
The real answer is that the .slug() jQuery plug-in is quite poorly written. It's something like 20 lines of code and it's full of mistakes.
The proper place to fix this issue in in that plugin so it only has to be done once, not everywhere you use it. I would suggest changing that plug-in to this code which fixed a bunch of issues:
//
// jQuery Slug Generation Plugin by Perry Trinier (perrytrinier#gmail.com)
// Licensed under the GPL: http://www.gnu.org/copyleft/gpl.html
jQuery.fn.slug = function(options) {
var settings = {
slug: 'slug', // Class used for slug destination input and span. The span is created on $(document).ready()
hide: true // Boolean - By default the slug input field is hidden, set to false to show the input field and hide the span.
};
// merge options and settings
if(options) {
jQuery.extend(settings, options);
}
// save jQuery object for later use in callback
if (this.length > 0) {
var self$ = this.eq(0);
jQuery(document).ready( function() {
if (settings.hide) {
jQuery('input.' + settings.slug).after("<span class="+settings.slug+"></span>");
jQuery('input.' + settings.slug).hide();
}
self$.keyup(function() {
var slugcontent = jQuery(this).val();
var slugcontent_fixed = slugcontent.replace(/\s/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase();
jQuery('input.' + settings.slug).val(slugcontent_fixed);
jQuery('span.' + settings.slug).text(slugcontent_fixed);
});
});
}
// return our jQuery object so it's chainable
return this;
};
This change fixes these issues with the prior code:
It will now work properly if you call it on an empty jQuery object
It will now work properly if called before the document is ready
It is no longer making unnecessary calls to turn things into jQuery objects that are already jQuery objects
If called on a selector with more than one object in it, it will only operate on the first item in the selector to prevent the problems that could occur otherwise
Made the keyup callback an anonymous function rather than creating a new variable.
Note: This plug-in is written to only work with one slug field per page. As such, you should probably be using it with an id value like "#title" rather than a class name like ".title" because the id will never return more than one field and never get you into trouble that way.
I've verified that this code works here: http://jsfiddle.net/jfriend00/CJJGz/.
You could add an if before the code is called -
if ($(".title").length)
You can test the length of the returned jQuery object, if it has a length of zero, the selector was not matched and the element was not found, e.g:
if ($(".title").length) {
$(".title").slug({
slug:'slug',
hide: false
});
}
You can check if the element exist by check its size:
if ($(selector).length) {
// Do what you want
}
You could check if the element exists like this:
jQuery(function() {
if($('.title').length != 0) {
$(".title").slug({
slug:'slug',
hide: false
});
}
});
You can test for the existence of the elements you're working on and if they exist perform the action/function:
if ($('.title').length){
$(".title").slug({
slug:'slug',
hide: false
});
}
I'm working on a validation project and I currently have it set up where my inputs are listed as objects. I currently have this code to setup and run the events:
setup method and functions used
function setup(obj) {
obj.getElement().onfocus = function() {startVal(obj)}
obj.getElement().onblur = function() {endVal(obj)}
}
function startVal(obj) {
obj.getElement().onkeyup = validate(obj)
}
function endVal(obj) {
obj.getElement().onkeyup = ""
}
Take note to how I have it where the onkeyup event should set when the object is receives focus, However when I activate the input it acts like I tagged the validate() function directly to the onfocus and it only validates when I initially focus the input.
edit the reason I have it set up this way is so that I don't have every single one of my form elements validating each time I launch an onkeyup event(which would be a lot since forms usually involve a decent amount of typing). I got it to work by simply attaching the validate() function to the onkeyup event. I just would prefer limit it this way so the there's no unnecessary processing.
Can you not set events with other events or is there something more specific that I'm doing wrong?
Any help is appreciated!
Here is some additional information that might help:
getElement Method
function getElement() {
return document.getElementById(this.id)
}
setEvents function
function setEvents() {
firstName.setup(firstName)
}
You are calling validate directly. Unless it is returning a function, it won't work (maybe you should have read my other answer more thoroughly ;)). I think you want:
obj.getElement().onkeyup = function() {validate(obj)};
And as I stated in my comment, there is no reason to add or remove the event handler on focus. The keyup event is only raised if the element receives input, so not when other elements receive input.