Need a javascript onclick decorator - javascript

I have legacy code that includes links with hard-coded onclick handlers (bad, I know).
link
I need to decorate that onclick function. In other words, I need to to replace the hard-coded onclick function with another function of my own that will first call the original function and then allow me to call my own code.
This is what I've tried, to no avail:
var oldFunc = $('a.linkClass').attr('onclick');
function newFunc(oldFunc) {
return function(oldFunc) {
oldFunc();
// my add'l code
}
}
$('a.linkClass').attr('onclick', '').unbind('click');
$('a.linkClass').click(newFunc(oldFunc));

First issue is need proper selector to find the onclick elements. $('[onclick]') will find all elements in page with that attribute. Also you need to remove the onclick attribute, trying to unbind with jQUery won't work
Assuming each onclick only has one function you could try:
$('[onclick]').each(function(){
var $el=$(this)
var funcName=$el.attr('onclick').replace('()','');
$el.click(window[funcName]).removeAttr('onclick');
});
Just realizing this won't work if any params in function

You are trying to wrap multiple events, but to do that you need to loop over the elements and wrap each one in turn (javascript can't store multiple functions in a single variable like that!) Also you get a string from onclick, so you'll need to eval it (which is generally a BadThing (tm). But the real bad thing here is that you're using onclick in the first place.
var elementsToFix = $('a.linkClass');
for( var i = 0; i < elementsToFix.length; ++ i ) {
var obj = elementsToFix.eq( i );
var oldFnc = obj.attr( 'onclick' );
obj.removeAttr( 'onclick' ).on( 'click', (function( curOldFnc ) {
return function( ) {
// pre-wrapper here
eval( curOldFnc );
// post-wrapper here
}
}( oldFnc )) );
}
This uses the super-evil eval. That's bad, and the only reason I'm using it is because you get a string from attr('onclick') instead of a function. The correct solution is to never use onclick in the first place.
jsfiddle example: http://jsfiddle.net/Jdn6V/1

As the function called from the onclick handler is in the global scope, the easy solution would be to just remove the onclick handler, store the string, and eval it later.
Of course eval is bad, and there are ways to parse the string, remove the parenthesis and keep any arguments, and call it as window[oldFunc] etc. but as a quick example, here it is with eval (which is evil)
$('a.linkClass').each(function() {
var old = $(this).attr('onclick');
$(this).attr('onclick', '').on('click', function(e) {
e.preventDefault();
eval.apply(window, [old]);
alert('new');
});
});
FIDDLE
without eval
$('a.linkClass').each(function() {
var old = $(this).attr('onclick'),
func = old.split('(')[0],
param = old.split('(')[1].replace(')','').split(',');
$(this).attr('onclick', '').on('click', function(e) {
e.preventDefault();
window[func].apply(window, param);
alert('new')
});
});
FIDDLE

.attr() always returns a string, so you'd have to eval it to execute the function.
You can get the original function this way, and not need to eval it later:
var oldFunc = $('a.linkClass')[0].onclick;
([0] picks the "pure" DOM element that has the onclick function.)
Now you can bind the function to the element:
$('a.linkClass').click( function(e) {
// new code here
// ...
oldFunc(e);
});
(e given as the parameter, just in case the old function uses it.)
The additional benefit of this is that it works regardless of what's in the old onclick attribute because it's wrapped in a function, so e.g. onclick="foo(); bar(); return false;" works correctly.

Related

jQuery find a class and create callback for its last element

In 'jQuery' I can execute callback for all elements of a class like this way -
jQuery(form_id).find(".multiple_upldprev").each(function () {
But I want to execute only last element of the class. I tried this way -
jQuery(form_id).find(".multiple_upldprev").last(function () {
It doesn't work.
How can I do that?
last() will reduce the set of matched elements to the final one in the set. And it does not take a handler. You can use:
$(...).last().each(function() {
});
Which usually doesn't make sense since .last() just returns the element:
var $lastElement = $(...).last();
// Do something with $lastElement
In cases where you have a predefined handler each might make sense:
$(...).last().each(myHandler);
// The same can roughly be archived with:
myHandler.call($(...).get(-1));
You dont event need a callback.
Since you are interested only in executing a function on last element, You can just do this with a self executing function and the last element in a variable.
(function(){
var $last = jQuery(form_id).find(".multiple_upldprev").last();
// now use this $last
})();
Or for some reason you want to use callback by using each but still execute the callback function only for the last element then you can do this with the help of .is()..
jQuery(form_id).find(".multiple_upldprev").each(function () {
var isLastElement = $(this).is(".multiple_upldprev:last"); //return true only for last element in loop
if(isLastElement ){
//execute your logic for last element
}
});

How may I call a method of the same object inside .append()?

generarGrilla() creates an output that has some html buttons.
Each button calls a method that exist in the same object guardarReserva()
When I load the page guardarReserva() is called without clicking any button, because this line console.log("whatever "+this.i); gets printed into the console.
If I do click one of the buttons that have the listeners, nothing happens.
var reservasAPP = {
i:0,
nombre: function () {
return $('#h09').val();
},
guardarReserva:function(){
var reservaConfirmada = $('#horario'+this.i).html('--> '+this.nombre());
console.log("whatever "+this.i);
return false;
},
generarGrilla: function(){
//it loads from html using jQuery(document).ready();
for(this.i=1; this.i<13; this.i++){
var impresionGrilla = $('#grilla').append("<button class=\"btn btn-primary btn-xs\" onclick=\"return "+this.guardarReserva()+"\">Reservar</button>");
};
},
}
You're actually running guardarReserva() within your .append() function. That is why you see the immediate console.log("whatever "+this.i); when you first open the browser.
I think you want the onclick of that button to call that function. For that (assuming reservasAPP is in the window namespace) you can do something like this:
var reservasAPP = {
i:0,
nombre: function () {
return $('#h09').val();
},
guardarReserva:function(){
var reservaConfirmada = $('#horario'+this.i).html('--> '+this.nombre());
console.log("whatever "+this.i);
return false;
},
generarGrilla: function(){
//it loads from html using jQuery(document).ready();
for(this.i=1; this.i<13; this.i++){
// ---Change is here ------------------------------------------------------------------------|
// V
var impresionGrilla = $('#grilla').append("<button class=\"btn btn-primary btn-xs\" onclick=\"return reservasAPP.guardarReserva();\">Reservar</button>");
};
},
}
Why I couldn't use "this" and I need to use "reservasAPP"
So, let's look at some of your original code (I'll slightly modify it so it is a bit shorter) and see why it wasn't working.
The line where the good stuff happens is within generarGrilla, within the for loop where you call the .append() function.
var i_g = $('#grilla').append('<button onclick="return ' + this.guardarReserva() + '">Reservar</button>');
Now you are correct in the idea that when that line of code executes, the this keyword is pointing at the reservasAPP object. However, what you are doing is not setting the onclick event for the button to run this.guardarReserva(), you are immediately running the this.guardarReserva() function.
Let's look at a small, semi-related example:
var n = function() {
return 'John';
};
var str = 'Hello, ' + n();
Now, what is the value of our variable str? Well, we are doing two things:
First, we have a string literal Hello, .
We are then concatenating it using the + operator with what the function n() returns.
Notice how we are going to use what n() returns rather than the literal 'n()' (string or function). This is because we are actually calling the n() function.
So, at the end of the day:
// str = 'Hello, John';
So, let's go back and look at your original code. What is actually going on there? Well, you are selecting all elements with id set to grilla (I'm assuming there is only one of those) then calling the jQuery append() function. .append() can accept a string as its argument, and it'll take that string, and insert it into the DOM as HTML.
Your string is concatenating three different things.
'<button onclick="return '
The return value of this.guardarReserva()
'">Reservar</button>'
Your guardarReserva function at the end returns false, which when concatenated with a string, uses its .toString() method, which in this case returns the actual word 'false'.
So, if you'd look at your HTML from before, you'd see your HTML code looked like:
<div id="grilla">
<button onclick="return false">Reservar</button>
</div>
Which is not at all what you wanted.
Instead, to fix that issue, I had you pass in one long string (which includes the function name you do want to call). So, your HTML ended up looking like:
<div id="grilla">
<button onclick="return reservasAPP.guardarReserva()">Reservar</button>
</div>
OK, so that's why the function was running right away, and why your button wasn't working. So we need to pass in the function as a string for the HTML button to know to run that function when it is clicked.
So, why can't you pass in the string:
'<button onclick="return this.guardarReserva()">Reservar</button>'
It has to do with how the browser evaluates the this keyword within that button.
In fact, let's do an experiment
<button onclick="console.log(this);">CLICK ME</button>
What happens when I click the button? You can do this experiment yourself. You'll see that it actually logs the literal button. Because within the buttons inline onclick, this refers to the button itself!
You can doubly verify this with this code:
<button id="button-1" onclick="console.log(this.id);">CLICK ME</button>
And see that it logs the string "button-1" (aka, the button's id).
So, if this refers to the button, we can't leave that context to get at our reservasAPP object! By referencing the reservasAPP object directly (assuming it was declared in your main <script></script> tag, thus placing it in the accessible window object), we can access that object, and thus its inner properties.
SIDE NOTE:
I would use a different method altogether for attaching our onclick handler.
I'd use jQuery's .on() function. Here is an example of how you could define generarGrilla:
generarGrilla: function() {
for (this.i = 1; this.i < 13; this.i++) {
// Save our `this` object for later
var self = this;
var button = $('<button class="btn btn-primary btn-xs">Reservar</button>');
// Set the `onclick` handler for our button (that isn't in the DOM yet)
button.on('click', function(event) {
// Use our saved `self` variable, which points to `reservasAPP`
return self.guardarReserva();
});
// `onclick` is set, insert our button into the DOM
var impresionGrilla = $('#grilla').append(button);
};
}
Now, when you run reservasAPP.generarGrilla();, it'll insert your buttons, and they'll all have onclick event handlers.
You can read this post on why'd some people think it's better to use attached event handlers vs inline JS on HTML attributes.
Have you tried reservasAPP.guardarReserva() instead of this.guardarReserva() ?

Writing sections of javascript dynamically

When the input gets blurred, I want to get the value of the input and replace the word "action" in the script, with the value of the input. So for example, if I type "hide" in the input, the word "action" would be replaced with "hide". Or something to that effect.
I'll then be using this logic to define the trigger (#elementA) and target (#elementB) also.
HTML
<input type="text" id="define-action">
<script id="interactions">
$('#elementA').click(function() {
$('#elementB').action();
)};
</script>
JS
$('#define-action').blur(function() {
var action = $(this).val();
// Replace "action" with action
)};
Use object [] notation
$(selector).hide(); can be written as $(selector)['hide']()
So you could pass in variable instead of hard code string
$(selector)[action]()
Beware you will get errors thrown if method represnted by variable doesn't exist, so you should probably create an array of valid methods you will accept and make sure value is in the array before calling the method
Example of testing values in array:
var allowedMethods=['hide','show','slideUp'];
if( $.inArray( action, allowedMethods) >-1){
$(selector)[action]()
}else{
alert( action +' is not a valid method')
}
Instead of dot notation use bracket notation
Also I don't think you need to use the blur handler, in the click handler you can read the value of the input filed
$('#elementA').click(function () {
var action = $('#define-action').val();
$('#elementB')[action]();
});
or if you want to use the blur method then you need to define the variable action is a shared scope of both the click and blur handlers
$('#elementA').click(function () {
$('#elementB')[action]();
});
var action;
$('#define-action').blur(function () {
action = $(this).val();
});

select html element id using jquery and .change()

I have several <select> boxes all using the same prefix and I would like to set up a recursive function to essentially do the work for me.
$('[id^="by_"]').change(function(e)
{
var elem = e;
console.log(e.value);
});
Based on this code is my intention pretty clear? Am I on the right track?
console prints out: undefined
I think you're on the right track - the selector you're using matches a prefix of "by_", and you're binding the change event to all of them. Make sure you put this in $(document).ready or similar. Are you having any problems with this code? Instead of using the e parameter, I would just use this inside of the function to refer to the element and $(this) to get the jQuery object of it. So to get the value, you'd use:
this.value
// or
$(this).val()
(ignore the e and elem stuff, although it wouldn't be a bad idea to store $(this) in something like elem so you can have a reference to it instead of re-creating the jQuery object every time you need it)
When using callbacks to events with jQuery, the (first) parameter of the callback is an event object that explains many things about the event that occurred ( http://api.jquery.com/category/events/event-object/ ) and does not hold the element - that's what this is for!
e in your code is the event object which has no value property, you should use this instead:
$('[id^="by_"]').change(function(e) {
var elem = this;
console.log(this.value);
});
Or if you want to use event object, you can use target property:
e.target.value
Since you're already using jQuery, why not something like this:
$('[id^="by_"]').change(function(e)
{
var $elem = $( this );
console.log( $elem.val() );
});
Isn't it more something like that:
$('[id^="by_"]').change(function()
{
console.log($('option:selected',this).val());
});
​
jsfiddle

jquery get the html code for all radios/checkboxes within a form

I am trying to parse some elements of a form.
I have with me the form ID/form name.
Now I want to parse through all radio buttons with name= "radio123"(or id="radio123").
But when I try $(this).html on each element (of a radio button)... then I get a blank value...
How do I access the HTML code for all radiobuttons/checkboxes within a form?
This is the normal behavior for jQuery.fn.html function: This method uses the browser's innerHTML property. Look at the examples if you don't understand what I mean.
I don't know why you want to get the HTML (if you want the value, look at the jQuery.fn.val method), but here's a solution
$("input:radio").each(function () {
console.log( this.outerHTML );
});
Be careful with the outerHTML, as it is not supported across all browsers you could use this function:
function getOuterHTML( node ) {
var parent = node.parentNode,
el = document.createElement( parent.tagName ),
shtml;
el.appendChild( node );
shtml = el.innerHTML;
parent.appendChild( node );
return shtml;
}
// use it like getOuterHTML( this ) in the preceding each loop
Something like this should work -
var htmlarr = [];
$("input[type='radio'][name='radio123'],[type='radio'][id='radio123']").each(function () {
//any logic you need here
htmlarr.push($(this).clone().wrap('<div>').parent().html());
})
Demo - http://jsfiddle.net/tJ2Zc/
The code uses this method $(this).clone().wrap('<div>').parent().html() to get the outer HTML of a DOM element. More info can be found in the question - Get selected element's outer HTML
The code above writes all the radio button html into an array but you could change it to do what you wished within the loop.

Categories

Resources