views:

119

answers:

5

I am using Rails and jQuery, making an ajax call initiated by clicking a link. I setup my application.js file to look like the one proposed here and it works great. The problem I'm having is how can I use $(this) in my say.. update.js.erb file to represent the link I clicked? I don't want to have to assign an ID to every one, then recompile that id in the callback script..

EDIT To give a simple example of something similar to what I'm trying to do (and much easier to explain): If a user clicks on a link, that deletes that element from a list, the controller would handle the callback, and the callback (which is in question here) would delete the element I clicked on, so in the callback delete.js.erb would just say $(this).fadeOut(); This is why I want to use $(this) so that I dont have to assign an ID to every element (which would be the end of the world, just more verbose markup)

application.js

jQuery.ajaxSetup({ 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript,application/javascript,text/html")} })

function _ajax_request(url, data, callback, type, method) {
    if (jQuery.isFunction(data)) {
        callback = data;
        data = {};
    }
    return jQuery.ajax({
        type: method,
        url: url,
        data: data,
        success: callback,
        dataType: type
    });
}

jQuery.extend({
    put: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, 'PUT');
    },
    delete_: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, 'DELETE');
    }
});

jQuery.fn.submitWithAjax = function() {
    this.unbind('submit', false);
    this.submit(function() {
        $.post(this.action, $(this).serialize(), null, "script");
        return false;
    })
    return this;
};

// Send data via get if <acronym title="JavaScript">JS</acronym> enabled
jQuery.fn.getWithAjax = function() {
    this.unbind('click', false);
    this.click(function() {
        $.get($(this).attr("href"), $(this).serialize(), null, "script");
        return false;
    })
    return this;
};

// Send data via Post if <acronym title="JavaScript">JS</acronym> enabled
jQuery.fn.postWithAjax = function() {
    this.unbind('click', false);
    this.click(function() {
        $.post($(this).attr("href"), $(this).serialize(), null, "script");
        return false;
    })
    return this;
};

jQuery.fn.putWithAjax = function() {
    this.unbind('click', false);
    this.click(function() {
        $.put($(this).attr("href"), $(this).serialize(), null, "script");
        return false;
    })
    return this;
};

jQuery.fn.deleteWithAjax = function() {
    this.removeAttr('onclick');
    this.unbind('click', false);
    this.click(function() {
        $.delete_($(this).attr("href"), $(this).serialize(), null, "script");
        return false;
    })
    return this;
};

// This will "ajaxify" the links
function ajaxLinks(){
    $('.ajaxForm').submitWithAjax();
    $('a.get').getWithAjax();
    $('a.post').postWithAjax();
    $('a.put').putWithAjax();
    $('a.delete').deleteWithAjax();
}

show.html.erb

<%= link_to 'Link Title', article_path(a, :sentiment => Article::Sentiment['Neutral']), :class => 'put' %>

The combination of the two things will call update.js.erb in rails, the code in that file is used as the callback of the ajax ($.put in this case)

update.js.erb

// user feedback
$("#notice").html('<%= flash[:notice] %>');

// update the background color
$(this OR e.target).attr("color", "red");
A: 

If your JS is coming from the server, there is really no way that $(this) can operate in the same context. The closest you could get would be to load some script from the server and eval it in the context of your client-side function.

I basically have an id for each of the DOM elements I need to manipulate, and refer to them within my scripts. It is occasionally ugly, but the alternatives are worse.

Toby Hede
I'm not gonna vote this down, but it's simply not true. I used to think this too, but see my post above.
Dan Beam
A: 

If your JS is coming from the server, there is really no way that $(this) can operate in the same context. The closest you could get would be to load some script from the server and eval it in the context of your client-side function.

Not true

I basically have an id for each of the DOM elements I need to manipulate, and refer to them within my scripts. It is occasionally ugly, but the alternatives are worse.

I don't think this is ugly.

The key to this problem is functional scoping. Let me show you what I mean. You need to create a function that is called before you send your XHR call. In your case, you're doing it with a click event, so let me show you an example tailored for you:

$( '#somelink' ).click( function( )
{
    // this stores the location of the current "this" value
    // into this function, and will available even after we
    // end this function (and will still live while the XHR's
    // callback is being executed

    var theLink = this;

    // fire the AJAX call

    $.post
    (
        'some/url',

        { 'some':'data' }, // optional

        function( )
        {
            // use theLink however you want
            // it'll still be there

            // also, if you're sending in callbacks
            // as variables, you can safely say

            hideAndStore.call( theLink, data );

            // which executes callback( ), overriding
            // "this" with theLink (your DOM node)
            // and sending in the responseText as the
            // first argument
        }
    );
} );

and then you could make your callback something like:

function hideAndStore( response )
{
    // you can safely use "this" as the DOM node
    $( this ).css( { 'display':'none' } );

    // and you can do whatever you wish with the response
    window.globalData = response;
}

where you'd make it do whatever you actually want it to do, haha.

For more info about functions in JavaScript that change the "this" value, check out .apply and .call at MDC

https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Function/Apply https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/Function/Call

Dan Beam
Seems like a good solution. What I'm looking for is a way to provide my callback the object so that I can refer to it. So, using the method that the example i posted uses {http://pastie.org/809431} with the callback "script" (i think) - can i modify that to make the script the body of the method you suggest i call? callbackMethod.call(_partial, theLink, data) where partial is the script
Rabbott
_partial = the script.. update.js.erb for example
Rabbott
you can't make it execute a file, but you can add a script tag to the DOM (`$( 'body' ).append( '<script src="whatevs.js"></script>' );`) or simply include script before you do the AJAX call and wrap the script's content in a function `myExtraJavaScript(){ /* put the code here */ }` then call `myExtraJavaScript` as or in the callback
Dan Beam
Guess I'm just going to have to assign ID's, this seems to be too complicated and too much of a security risk to implement.
Rabbott
A: 

What are you doing in the javascript you are sending back? Maybe you can send back some html or json and operate on it in a callback.

$('a:clickable').bind('click', function() {
  var elem = $(this);
  $.ajax({
    url: ...,
    dataType: 'json',
    success: function(data) {
      // elem is still in scope
      elem.addClass(data.class_to_add_to_link);
    }
  });
});
fullware
Well to give a simple example of something similar to what I'm trying to do (and much easier to explain): If a user clicks on a link, that deletes that element from a list, the controller would handle the callback, and the callback (which is in question here) would delete the element I clicked on, so in the callback delete.js.erb would just say $(this).fadeOut(); This is why I want to use $(this) so that I dont have to assign an ID to every element (which would be the end of the world, just more verbose markup)
Rabbott
I'm not sure what you mean by the "controller would handle the callback". Your front-end javascript "callback" would be the success handler as I wrote above. The rails controller would respond with some status or some new html or whatever. If the call was successful, the front-end here knows all it needs, what was clicked, the context, any closure variables, etc. Or maybe I'm still not understanding what (and why) you're trying to do in your rails controller and why it can't be done client-side.
fullware
+1  A: 

jQuery already handles the this issue for you with the event properties:

$("a").click(function(e){
  e.preventDefault();
  $("#foo").fadeIn(3000, function(){
    $(e.target).text("Foo loaded");
  });
});​​​

Note how I can refer back to the main link via its event. This is the case with any events that are handled within as well. Just give them unique names, such as e2, e3, etc. No more need to constantly write yet another var item = $(this) line to keep track of this three events back.

Online Demo: http://jsbin.com/egelu3/edit

Jonathan Sampson
This is great, and works outside of the script - so its CLOSER! Here is the javascript that gets called http://pastie.org/832155 so you can see the click event has 'e' provided, but you also see 'script', that script apparently is not aware of e.. and that script is where im trying to get access to $(this) or e in your case, do you see a way to provide it?
Rabbott
I'm not familiar with `$.put()`. Is `"script"` a string representing a function to call? If so, can you not pass `e` in as an argument to that function?
Jonathan Sampson
script is actually the 'type' parameter of the put method which is something i have working from a tutorial.. This site http://homework.nwsnet.de/news/9132_put-and-delete-with-jquery has something similar. I'm sorry I don't know much about it either..
Rabbott
What are you using this method on? Can you post an example of that?
Jonathan Sampson
Just added example code in the original question
Rabbott
A: 

This cannot be accomplished, The method in which i am trying to do this makes it impossible, i cannot pass references to javascript objects through views.

Solution was to assign IDs to each item, and refer to them by that.

Rabbott