1

I have a checkout I'm playing with which has some up and down arrows next to the quantity input of each item.

I have some repeated code and it doesn't seem that efficient. Hopefully you can help.

Each item looks something like this (please excuse my non-standard-conforming item_id attribute for now)

<div class="qty_ctrl_wrap">
  <input class="bag_qty" type="text" value="4" item_id="532" name="product_quantity">
  <div class="arrows">
    <div class="more"></div>
    <div class="less"></div>
  </div>
</div>

Here's the full jquery function:

$('#checkout .arrows div').live('click',function(){

    var item_id = $(this).parent().prev('input').attr('item_id');
    var current_qty = $(this).parent().prev('input').val();

    if($(this).attr('class') == 'more') {

        var new_qty = parseInt(current_qty) + 1;

    } else {

        var new_qty = parseInt(current_qty) - 1;

    }

    // change the input on screen for visual reasons...
    $(this).parent().prev('input').val(new_qty);


    // submit the changes
        $.ajax({
        type: 'POST',
        url: '/backend/product_purchase_items.php',
        data: 'qty='+new_qty+'&item_id='+item_id,
        cache: false,
        success: function(data) {
          $('#product_purchase_items').html(data);
        }
        });     

});

As you can see, I'm repeating the following thing 3 times:

$(this).parent().prev('input')

I've been trying to work out how to put that in a variable so I don't have to repeat that code.

But in addition to that, the way this works is that if you hit the arrow 3 times, it does 3 seperate ajax posts. Ideally there would be a slight pause before sending the post.

How does one make a pause of say 300 milliseconds to see if there are further clicks of the arrows before submitting the post?

willdanceforfun
  • 11,044
  • 31
  • 82
  • 122

3 Answers3

5

You can use setTimeout to debounce the event:

$('#checkout .arrows div').live('click',function(){
    var $this = $(this),
        data = $this.data(),
        $item = $(this).parent().prev('input');


    if(data['debounce_timer']) {
        clearTimeout(data['debounce_timer']);
    }

    $item.val(function(i, value) {
        return +value + $this.hasClass('more') ? 1 : -1;
    });

    // Maybe you have to store a reference to the quantity (value) here. 
    // Otherwise you might get strange results when the arrow was just 
    // clicked again when the callback is executed.

    var timer = setTimeout(function() {
        $.ajax({
            type: 'POST',
            url: '/backend/product_purchase_items.php',
            data: {qty: $item.val(), item_id: $item.attr('item_id')}
            cache: false,
            success: function(data) {
              $('#product_purchase_items').html(data);
            }
        });    
    }, 150); // <-- try different times

    data['debounce_timer'] = timer;
});

Things that changed/should be considered:

  • Caching $(this).parent().prev('input') in a variable to not traverse the DOM every time to find the same element again.

  • Passing a function to .val() [docs] and update the value "in one go".

  • Using unary + to convert a string to a number, instead of parseInt. If you use parseInt, you have to pass the radix as second parameter (in your case 10), to prevent JS from interpreting numbers with leading zeroes as octal numbers.

  • Using .hasClass() [docs] instead of comparing the class attribute. If the element has more than one class, the comparison will fail.

  • Setting an object as value of the data option instead of a string. This ensures that the data is properly encoded.

  • You really should use HTML5 data attributes instead of custom attributes (item_id). Even if the browser does not support HTML5 (yet), this is the safer way. With jQuery, you can even access them via the .data() method.

  • Using setTimeout [MDN] to delay the Ajax call and storing a reference to the timer ID in .data() [docs]. When the handler is rapidly executed, a previous timer will be cancelled with clearTimeout [MDN], to prevent too many Ajax requests.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • I am really grateful for all the other answers... but this one Felix is so great!! Firstly, html 5 data attr's - thank god... I can say bye to my hacks.. the other things I'm just sitting here studying for now trying to wrap my head around the new concepts. A lot of things I had no idea about. Thanks very much. – willdanceforfun Aug 24 '11 at 01:54
  • Thanks again. I think there might be a little hitch I'm trying to debug as when I click more or less arrows I get '1' being the qty. Still wrapping my brain around this $item.val function you made. Its like a simplified if else statement with math rolled in one?? – willdanceforfun Aug 24 '11 at 02:41
  • 1
    @KeenLearner: Yes. It takes the current value, converts it to a number and then adds or subtracts `1` based on whether the clicked elements has class `more` or not. I don't see what could be wrong with that. – Felix Kling Aug 24 '11 at 08:02
  • there's nothing wrong with that - you're right. But if I do an alert($item.val()); directly under the code where the $item.val function is, I get '1' whether I am clicking more or less. – willdanceforfun Aug 24 '11 at 09:35
  • If I redo that function in my primitive coding style it all works a charm. Would love to know how and what you're doing with this short hand function though. – willdanceforfun Aug 24 '11 at 09:42
  • 1
    Mmh. There is nothing special with it. The `x ? y : z` is the conditional operator and is basically a short `if - else`. When you pass a function to `val`, then the new value will be set to the return value of that function. Two arguments are passed to the function, the index of the element in the selected set and the current value. – Felix Kling Aug 24 '11 at 09:57
2

The answer to your first question is simple:

var associatedInput = $(this).parent().prev('input');
// which you use like this:
var current_qty = associatedInput.val();

As far as implementing a slight delay, here's one way to do it:

// following var declared outside your click handler, though not
// necessarily global because your code should probably be in a
// document ready handler:
var timerID = null;

// following within the click handler where your current $.ajax call is:
if (timerID != null) {
  clearTimeout(timerID);
}

timerID = setTimeout(function() {
                       timerID = null;
                       $.ajax(/*your existing ajax code/options here*/);
                     }, 300);

In case it's not obvious, the first time your click handler is called it will set a timeout to delay the ajax call. On second and subsequent clicks if there's already a timer ticking away it will be cleared (cancelled) and a new timeout set. So this keeps putting off the ajax call until the minimum 300 milliseconds has passed since the last click.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
1

I would use something like:

var $input = $(this).closest("input.bag_qty");

Also, the variable new_qty is out of scope, you could do:

var new_qty = $(this).hasClass('more') ? parseInt(current_qty, 10) + 1 : parseInt(current_qty, 10) - 1;
Dave L.
  • 9,595
  • 7
  • 43
  • 69