nested object events - javascript

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.

Related

Wrap jquery event handler around hardcoded handler

I have several forms which look like this:
<form id=myForm onsubmit="saveFormData(this);return false">
....
</form>
Upon submission, a custom function is executed and the default action (sending the data through http) is canceled.
But now I need to validate the form and if it validates ok, then trigger the custom function (saveFormData in this case, but it may be different for other forms), otherwise do nothing.
So the final event handler should work like this:
$('#myForm').submit(function(){
if(formValidatesOk(this))
saveFormData() ;
return false ;
}
However, I cant make changes to the HTML code, so I need a general way of redefining the onsubmit event by wrapping the hardcoded handler with a jquery event handler.
The first thing that comes to my mind is something like (untested):
var hardcodedHandler = $('#myForm').prop('onsubmit') ; // save the current handler
$('#myForm').prop('onsubmit',null) ; // remove the current handler
$('#myForm').submit(function(){
if(formValidatesOk(this)) // if the form is valid then...
Function(hardcodedHandler).call(this) ; // trigger the original handler
return false ;
}) ;
But that's not very elegant (to say the least).
Do you know of a better way of doing it?
You can just take the old function from the onsubmit property of the element, you don't need to eval it:
var form = $('#myForm');
var oldHandler = form.prop("onsubmit");
form.removeProp("onsubmit").submit(function(e){
e.preventDefault();
if(formValidatesOk(this))
oldHandler.call(this, e);
});
Demo at jsfiddle.net
Events are unpredictable (in the way they get fired). I think that handers, bounded to them, must not rely on order either. So binding a handler to a event which changes the previous added handler is certainly a (very) bad practice. And indeed, as you suggested, you can never be sure which handler will be triggered first. After all, events should have no notice of each other. One should use events and handlers to avoid high coupling of the code.
The best way to approach this issue (taking your constraints into account) is to decorate your 'old' handler with the new behaviour. Bergi posted a nice example of this.

Getting an assigned jQuery variable to "re-query"

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

Call an object as a function in JavaScript

I'm trying to execute JavaScript functions that are called when a event (for example onClick event) is performed on a web page with JavaScript code. I'm getting the function from the event like this :
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
and I'm trying to execute this object (which a JavaScript function in fact) as a function (suppose we have <a onClick = alert('whatever');> on this example, I tried:
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
attributval() = function(){attributval};
attributval();
but it didn't work.
A DOM attribute is not the same as a JavaScript property (even though they can have the same name onclick). You should use
var attributval = document.getElementsByTagName("a")[0].onclick;
to retrieve a function (or null) from the JS object (as opposed to getAttribute(), which will most likely return a toString() for the property).
Now, attributval() = is illegal syntax, as attributval() is not an l-value (you cannot assign to it).
attributval(); will work but without the second line (which is illegal JavaScript) it will invoke the original A element onclick handler (if one is defined) or throw an exception (if the onclick handler is null).
Skip trying to create a function around the function. Just call it:
var attributval = document.getElementsByTagName("a")[0].onclick;
attributval();
try
var attributval = document.getElementsByTagName("a")[0].getAttribute('onClick');
By using get attribute you are returning a string so your only way is to use eval(onclickString) or var fn = new Function(onClickString); fn();
attributval is simply a string, correct? If you trust this code, execute it with eval(attributval) -- however any reference to this won't work.
What you probably want is to manually trigger an event. jQuery makes that easy.
If you want to do more than a click, then Chris McDonald's answer at Is it possible to trigger a link's (or any element's) click event through JavaScript? seems to fit the bill, although you might need to heed the third comment.
I thought I'd add a short answer on how to work with events using jQuery, since it seems relevant.
// Select the link using it's ID field (assuming it has one)
var myLink = $('a#myLink')
// Add a click event to the link
myLink.on('click', function(e) {
console.log("I've been clicked!");
});
// Trigger the click event manually. This would result in the above
// function being run. Interestingly, this will not cause the browser
// to follow the link like a real click would
myLink.trigger('click');
// Remove the click event (this removes ALL click events)
myLink.off('click');
// Add a click event to the link that only runs once, then removes itself
myLink.one('click', function() {
alert("I'll only bother you once!");
});
// Add a click event that you can identify from other click events.
// This means that you can trigger it or remove it without bothering other
// click events
myLink.on('click.myClick', function() {
alert("This click event has been identified as 'myClick'");
});
// Now you can trigger it without triggering other click events
myLink.trigger('click.myClick');
// And remove it, also with no harm coming to other click events
myLink.off('click.myClick');
Hope this helps

Can I control the order in which javascript / jQuery events fire?

Background
I've got asp.net webform with a grid, and when users update textboxes in that grid, the onchange event kicks off a WebMethod call and updates the rest of the changed row. Nothing is saved at that time -- we're just updating the UI.
To commit the changes, you click the save button.
This actually works reliably in almost every scenario. However, there is one very persistant one that it feels like I should be able to solve, but it's time to call in the specialists.
The Problem Scenario
I'm using jQuery to capture the enter key, and unfortunately that event fires first, causing the page to submit before the callback completes. The row is not updated correctly. Stale and bewildering data is saved.
Update
I don't think you can make the enter behavior depend on the callback, because you could save without changing a row. In that case, if you didn't change a row, it would never save.
Now if there was some way to inspect javascript's internal list of things to do, or maybe create my own and then manage it somehow, that would work. But that's some heavy lifting for something that should be easy. So unless an expert tells me otheriwse, I have to assume that's wrong.
Attempts
Right now I'm using the built-in jQuery events and I've got this elaborate setTimeout persisting the fact that a save was attempted, pausing long enough for the WebMethod to at least get called, and relying on the callback to do the submit. But it turns out javascript ansychrony doesn't work the way I hoped, and the onchange event doesn't even fire until that chunk of code completes. That was surprising.
I was thinking I could use my own little object to queue up these events in the right order and find a clever way to trigger that, etc.
This all seems like the wrong direction. Surely this is insane overkill, this is a common problem and I'm overlooking a simple solution because I don't work in javascript 24/7.
Right?
Code
Here's what I've got right this minute. This obviously doesn't work -- I was trying to take advantage of the async nature of jquery, but all of this apparently has to conclude before the row's onchange event event fires:
$(document).bind("keypress", function (e) {
if (e.keyCode == 13) {
handleEnter();
return false; //apparently I should be using e.preventDefault() here.
}
});
function handleEnter() {
setTimeout(function () {
if (recalculatingRow) { //recalculatingRow is a bit managed by the onchange code.
alert('recalculating...');
return true; //recur
}
//$('input[id$="ButtonSave"]').click();
alert('no longer recalculating. click!');
return false;
}, 1000);
}
And then a typical row looks like this. Note that I'm not using jquery to bind this:
<input name="ctl00$MainContent$GridOrderItems$ctl02$TextOrderItemDose" type="text" value="200.00" maxlength="7" id="ctl00_MainContent_GridOrderItems_ctl02_TextOrderItemDose" onchange="recalculateOrderItemRow(this);" style="width:50px;" />
I could post the code for recalculateOrderItemRow, but it's really long and right now the problem is that it doens't fire until the after keypress event concludes.
Update Dos
According to Nick Fitzgerald (and man is that a cool article) the use of setTimeout should cause this to become async. Digging further into interactions between setTimeout and jQuery, as well as interactions between normal javascript events and jQuery events.
Preventing ENTER shouldn't be causing you so much trouble! Make sure you have something like this on your code:
$(document).on('keydown', 'input', function(e) {
if(e.keyCode == 13) {
e.preventDefault();
}
});
UPDATE
It looks like you do want to save on ENTER, but only after the UI is updated on change. That is possible. You could use a flag a Matthew Blancarte suggested above, trigger save from the change callback, and get rid of the setTimeout.
But I wouldn't recommend that. You are better off relying solely on the save button for saving. If you don't, your users will have to wait for two async operations to complete before saving is finished. So you'd have to block the UI, or keep track of all async operations, aborting some as needed. I think it's not worthy, ENTER becomes less intuitive for the users if saving takes too long.
The hideous mass of workarounds below, which effectively took me all day today and half of yesterday to write, seems to solve every permutation.
The amusing thing is that enter itself doesn't trigger onchange, if you call e.preventDefault(). Why would it? The change doesn't actually happen until the default behavior of clicking the save button occurs.
Very little else about this is amusing.
//Used in handleEnter and GridOrderItems.js to handle a deferred an attempt to save by hitting enter (see handleEnter).
var isSaving = false;
var saveOnID = '';
//When one of the fields that trigger WebMethods get focus, we put the value in here
//so we can determine whether the field is dirty in handleEnter.
var originalVal = 0;
//These fields trigger callbacks. On focus, we need to save their state so we can
//determine if they're dirty in handleEnter().
$('[id$=TextOrderItemDose], [id$=TextOrderItemUnits]').live("focus", function() {
originalVal = this.value;
});
$(document).bind("keypress", function (e) {
if (e.keyCode == 13) { //enter pressed.
e.preventDefault();
handleEnter();
}
});
//Problem:
//In the products grid, TextOrderItemDose and TextOrderItemUnits both have js in their onchange events
//that trigger webmethod calls and use the results to update the row. Prsssing enter is supposed to
//save the form, but if you do it right after changing one of those text fields, the row doesn't always
//get updated due to the async nature of js's events. That leads to stale data being saved.
//Solution:
//First we capture Enter and prevent its default behaviors. From there, we check to see if one of our
//special boxes has focus. If so, we do some contortions to figure out if it's dirty, and use isSaving
//and saveOnID to defer the save operation until the callback returns.
//Otherwise, we save as normal.
function handleEnter() {
var focusedElement = $("[id$=TextOrderItemDose]:focus, [id$=TextOrderItemUnits]:focus")
//did we press enter with a field that triggers a callback selected?
if (isCallbackElement(focusedElement) && isElementDirty(focusedElement)) {
//Set details so that the callback can know that we're saving.
isSaving = true;
saveOnID = focusedElement.attr('id');
//Trigger blur to cause the callback, if there was a change. Then bring the focus right back.
focusedElement.trigger("change");
focusedElement.focus();
} else {
forceSave();
}
}
function isCallbackElement(element) {
return (element.length == 1);
}
function isElementDirty(element) {
if (element.length != 1)
return false;
return (element.val() != originalVal);
}
function forceSave() {
isSaving = false;
saveOnID = '';
$('input[id$="ButtonSave"]').click();
}
This gets called in the change event for the textboxes:
function recalculateOrderItemRow(textbox) {
//I'm hiding a lot of code that gathers and validates form data. There is a ton and it's not interesting.
//Call the WebMethod on the server to calculate the row. This will trigger a callback when complete.
PageMethods.RecalculateOrderItemRow($(textbox).attr('id'),
orderItemDose,
ProductItemSize,
orderItemUnits,
orderItemUnitPrice,
onRecalculateOrderItemRowComplete);
}
And then, at the end of the WebMethod callback code we pull the updated form values out, put the caret where it needs to be using jquery.caret, and check to see if we need to force a save:
function onRecalculateOrderItemRowComplete(result) {
var sender, row;
sender = $('input[id="' + result.Sender + '"]');
row = $(sender).closest('tr');
row.find('input[id$="TextOrderItemDose"]').val(result.Dose);
row.find('input[id$="TextOrderItemUnits"]').val(result.Units);
row.find('span[id$="SpanTotalPrice"]').html(formatCurrency(result.TotalPrice));
calculateGrandTotalPrice();
$(document.activeElement).select();
if (isSaving && saveOnID == result.Sender) {
forceSave();
}
}
result.Sender is the ID of the calling control, which I stuffed into the WebMethod call and then returned. saveOnID may not be perfect, and it might actually be even better to maintain a counter of active/uncallback-ed WebMethod calls to be totally sure that everything wraps up before save. Whew.
Can you post your javascript? Sounds like you're on the right track. I would change my OnChange events to increment a variable before making the AJAX call. I'll call the variable inProcess and initialize it to zero. When the AJAX call comes back, I would update the inProcess to the current value minus one. On the Enter key event, I would check to that inProcess equals zero. If not, you could either warn the user or set a timeout to try again in a bit.
You could unbind the Enter key capture while you are in the onChange event, then rebind it at the end of the callback function. If you post some code, I could give a more specific answer.
It sounds like you shouldn't be calling the WebMethod asynchronously. Call it synchronously, and on success, save your data.

Listening and firing events with Javascript and maybe jQuery

In my JavaScript and Flex applications, users often perform actions that I want other JavaScript code on the page to listen for. For example, if someone adds a friend. I want my JavaScript app to then call something like triggerEvent("addedFriend", name);. Then any other code that was listening for the "addedFriend" event will get called along with the name.
Is there a built-in JavaScript mechanism for handling events? I'm ok with using jQuery for this too and I know jQuery makes extensive use of events. But with jQuery, it seems that its event mechanism is all based around elements. As I understand, you have to tie a custom event to an element. I guess I can do that to a dummy element, but my need has nothing to do with DOM elements on a webpage.
Should I just implement this event mechanism myself?
You have a few options:
jQuery does allow you to do this with objects not associated with the document. An example is provided below.
If you're not already using jQuery on your page, then adding it is probably overkill. There are other libraries designed for this. The pattern you are referring to is called PubSub or Publish/Subscribe.
Implement it yourself, as you've suggested, since this is not difficult if you're looking only for basic functionality.
jQuery example:
var a = {};
jQuery(a).bind("change", function () {
alert("I changed!");
});
jQuery(a).trigger("change");
I would implement such using MVVM pattern with knockjs library.
Just create an element, and use jquery events on it.
It can be just a global variable, doesn't even have to be connected to the DOM.
That way you accomplish your task easily and without any extra libs.
Isn't it possible to bind onchange events in addition to click events? For instance, if addFriend is called and modifies a list on the page, you could bind the change event to then invoke additional functionality.
$('#addFriendButton').click( function() {
// modify the #friendList list
});
$('#friendList').change( function() {
myOtherAction();
});
This is total Host independent, no need for jQuery or dom in this case!
function CustomEvents(){
//object holding eventhandlers
this.handlers_ = {};
}
//check if the event type does not exist, create it.
//then push new callback in array.
CustomEvents.prototype.addEventListner = function (type, callBack){
if (!this.handlers_[type]) this.handlers_[type] = [];
this.handlers_[type].push(callBack);
}
CustomEvents.prototype.triggerEvent = function (type){
//trigger all handlers attached to events
if (!this.handlers_[type]) return;
for (var i=0, handler; handler = this.handlers_[type][i]; i++)
{
//call handler function and supply all the original arguments of this function
//minus the first argument which is the type of the event itself
if (typeof handler === "function") handler.apply(this,arguments.slice(1));
}
}
//delete all handlers to an event
CustomEvents.prototype.purgeEventType = function(type){
return delete this.handlers_[type];
}
test:
var customEvents = new CustomEvents();
customEvents.addEventListner("event A", function(arg){alert('Event A with arguments' + arg);));
customEvents.triggerEvent("event A", "the args");
EDIT added arguments passing

Categories

Resources