views:

44

answers:

2

I have a page that displays a grid of products that is populated programmatically on the server-side based on a database query. In each grid cell I have a drop-down list where the user can rate the product. I also have a <div> in each grid cell that shows the current average rating. My aim is to trigger an ajax call when a rating is selected that will update the database on the backend. I then want to update the average rating to display the new average. Make sense?

First things first, here's how I get the just-made rating back to the server. In my $(document).ready event handler I add a change event handler to all <select> elements in the page that have an id that contains ddlRatingOptions. In the event handler I get the ProductID and Rating associated with the drop-down list that was changed. I then make an ajax call back to the server, passing along the values.

$("#products select[id*='ddlRatingOptions']").change(function () {
    // Determine the ProductID for the product that was just rated
    var productId = $(this).attr('ProductID');
    var rating = $(this).val();

    // Send the rating and productId back to the server
    $.ajax({
        url: '<%=Page.ResolveClientUrl("~/Services/RateProduct.ashx") %>',
        data: {
            ProductID: productId,
            Rating: rating
        },
        cache: false,
    });
});

This works swimmingly.

What I need now, though, is to get back the average rating and update the user interface. I can easily return the average rating as part of the response of the ajax call, but what's tripping me up is I'm unsure of how to reference the <div> that contains the average rating for this product.

What I have now works, but it feels like a hack. I'm hoping there's an easier way. In short, I locate the <div> and then send its id to the server when making the ajax request. The server-side code echoes it back (along with the average rating). I then, in the success event handler, locate the <div> by that echoed id and update its text accordingly.

$("#products select[id*='ddlRatingOptions']").change(function () {
    ...

    // Determine the "Current Average Rating" element that needs to be updated after the ajax call completes
    var currentRating = $(this).parent().siblings(".currentRating");

    $.ajax({
        url: '<%=Page.ResolveClientUrl("~/Services/RateProduct.ashx") %>',
        data: {
            ProductID: productId,
            Rating: rating,
            CurrentRatingId: currentRating.attr('id')
        },
        cache: false,
        dataType: 'json',
        success: function (results) {
            // Update the current rating with the new average rating              
            $("#" + results.CurrentRatingId).text(results.AverageRating + ' Stars');
        }
    });
});

As you can see, the Current Average Rating <div> id gets passed along to the server and the success event handler is passed a JSON object that includes two properties: the AverageRating and the CurrentRatingId (which is just echoed back from what is sent to the server).

Is there a cleaner way to do this?

Thanks!

+3  A: 

Why not just store the element to a variable? This will scope it to the outer function, making it available to the inner (success) function. You're (sort of) already doing this with the currentRating variable, so it's just a case of re-using that variable later, instead of looking up the ID:

$("#products select[id*='ddlRatingOptions']").change(function () {
    ...

    // Determine the "Current Average Rating" element that needs to be updated after the ajax call completes
    var currentRating = $(this).parent().siblings(".currentRating").eq(0);

    $.ajax({
        url: '<%=Page.ResolveClientUrl("~/Services/RateProduct.ashx") %>',
        data: {
            ProductID: productId,
            Rating: rating,
            // Do you need this now?
            CurrentRatingId: currentRating.attr('id')
        },
        cache: false,
        dataType: 'json',
        success: function (results) {
            // Update the current rating using the variable we stored above    
            currentRating.text(results.AverageRating + ' Stars');
        }
    });
});

I added .eq(0) to make sure we're only grabbing the first element (whose ID attribute would be sent to the server anyway).

Andy E
That's slick! I didn't realize that the success function in the ajax call could reference variables defined outside of its stack. Is there any concern here that things might get jumbled up, like if the user makes two ratings in quick succession, before the first rating request can be transmitted? Or if there are two quick ratings and the second rating response is returned before the first? Or am I overthinking things?
Scott Mitchell
@Scott: Each separate function call will create a new closure, containing all local variables within that closure. A new call to the same function means a new closure, new variables and a new request. I suggest reading up on function scope in JavaScript. https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope is a good place to start :-)
Andy E
@Andy E: Thanks for the follow up. Admittedly, closures and JavaScript's scoping rules are still a weak spot for me, I'll check out the link you recommended. Thanks again.
Scott Mitchell
A: 

I'm not sure if I got it right, but why don't you just use the id from the local var?

like $("#" + currentRating.attr('id')).text(results.AverageRating + ' Stars');

cripox
@cripox: See my comment to Andy E's answer. Thanks
Scott Mitchell
each currentRating will be a new var for each call, so you are safe
cripox