9

I would like to delay ajax from firing in the same way that the autocomplete seems to work. For example, if a user it typing, the ajax will not run until 500ms has elapsed since the last keyup.

I am currently looking at drupal.behaviors but not able to make it work.

Drupal.behaviors.mymodule = {
  attach: function(context, settings) { 
    $('input.andtimer', context).delay(500).ajaxStart();
  }
};

This is the form element to which the behavior is attached.

$form['my_input'] = array(
  '#type' => 'textfield',
  '#default_value' => $value,
  '#ajax' => array(
    'callback' => 'my_callback',        
    'event' => 'keyup',
    'wrapper' => 'my_wrapper',  
    'trigger_as' => array(
      'name' =>  'my_button',
  ),
  'progress' => array('type' => 'none'),
  ),
  '#attributes' => array(
    'class' => array('andtimer'),
  ),                      
);

This jsfiddle shows what I am trying to achieve.

Would How to override Drupal.ajax.prototype.beforeSend? be a route to take this down?

The following works for the first 'set' of inputs with the class .andtimer. It is not working for any other set, the ajax always continues with the first set. Any ideas how to fix this?

(function($, Drupal) {
    Drupal.behaviors.bform = {
        attach : function(context, settings) {

            var events = $('.andtimer').clone(true).data('events');
            $('.andtimer').unbind('keyup');
            var typingTimer;
            var doneTypingInterval = 300;
            $('.andtimer').keyup(function() {
                clearTimeout(typingTimer);
                typingTimer = setTimeout(doneTyping, doneTypingInterval);
                function doneTyping() {
                    $.each(events.keyup, function() {
                        this.handler();
                    });
                }

                return false;
            });
        }
    };
})(jQuery, Drupal); 

Using $form['my_input']['#ajax']['event'] = 'finishedinput' as suggested and

var typingTimer;
var doneTypingInterval = 600;

$('.andtimer').on('keyup', function (e) {
  clearTimeout(typingTimer);
  if ($(this).val) {
    var trigid = $(this);
    typingTimer = setTimeout(function(){                    
      trigid.triggerHandler('finishedinput');
    }, doneTypingInterval);
  }
});

Works for each 'group' of inputs where the count of filled inputs needs to be obtained.

4
  • That code doesn't have anything to do with keyup/keydown or the event binding you're alluding to - can you add your actual code please? Bear in mind that if you're just looking for general javascript help, this isn't the place to find it. The rule is: make it work outside Drupal first, and if you can't get it to work inside Drupal, ask here
    – Clive
    Jun 18, 2014 at 11:57
  • Thanks Clive, I've added the code to build the input. I went straight for the try and make it work in Drupal. Still learning. I'll give it a go outside and see if I can't clarify the prob a bit more in my head. Jun 18, 2014 at 12:20
  • I spoke too soon, didn't realise how tied to Drupal you're trying to make this. This makes a pretty interesting problem :)
    – Clive
    Jun 18, 2014 at 12:21
  • 1
    The bottom code snippet works well for me, except after firing the event the field loses focus. How can I make is so focus stays on the element after firing.
    – VanD
    Jul 2, 2015 at 20:15

5 Answers 5

7
+50

One option is to use a custom jQuery event, eg. something like finishedinput. Set $form['my_input']['#ajax']['event'] = 'finishedinput' and provide some JS to trigger your custom event after an appropriate delay (similar to the JS in the fiddle).

8
  • Amazing ! I was exactly looking for that :) Thanks a lot. However, if I could have a tip on how to apply this when you alter an input of a views exposed form. If I don't set callback to $form['my_input']['#ajax'] nothing happens, if I add the submit views exposed form submit fn as callback (or whatever else) it works but returns undefined index: form_build_id... And I don't know neither how and where to add a $form_state['rebuild'] = TRUE Thanks in advance
    – Kojo
    Jul 16, 2014 at 18:04
  • 1
    @Kojo Could you ask a new question about this and include descriptions of the Views AJAX settings, the exposed filter, any custom code you're using, and the problems that occur? Btw CTools autosubmit already has a (hardcoded) delay on submitting from textfields of 0.5s (see auto-submit.js).
    – Andy
    Jul 17, 2014 at 9:31
  • Ah great, here is the setting ! Isn't it possible to modify it "on the fly" for a specific input ? If it's not simple, I'll post a question :) Thanks for your help!
    – Kojo
    Jul 17, 2014 at 9:57
  • 1
    @Kojo Not as easily as it should be! If it was me, I'd probably just create my own auto-submit.js and use hook_js_alter() to ensure it's used in place of the original. (But really imho the ctools code should use Drupal.settings rather than a hard-coded value.)
    – Andy
    Jul 17, 2014 at 10:12
  • 1
    @Kojo Also take a look at drupal.org/node/2023705 which is about improving the autosubmit experience (not just for text inputs). There's a patch which might be enough for you. EDIT: and if you do try to use it, don't forget to put a comment on the issue saying whether it worked.
    – Andy
    Jul 17, 2014 at 11:16
3

You can implement your own Jquery event and add logic to delay the triggering.

Add this into my_module.js:

(function ($, Drupal, drupalSettings) {
  'use strict';
  Drupal.behaviors.my_module_finishedinput = {
    attach: function (context, settings) {
      var typingTimer;
      var delay = 1100;
      $('#form-input-id-goes-here').once('finished_input').on('keyup', function (e) {
        clearTimeout(typingTimer);
        if ($(this).val()) {
          var trigid = $(this);
          typingTimer = setTimeout(function () {
            trigid.triggerHandler('finishedinput');
          }, delay);
        }
      });
    }
  };
})(jQuery, Drupal, drupalSettings);

Add js to the page by creating my_module.libraries.yml.

jquery_event:
  version: VERSION
  js:
    my_module.js: {}
  dependencies:
    - core/jquery
    - core/jquery.once
    - core/drupal
    - core/drupalSettings

Attach js to the form

$form['myform']['#attached'] = ['library' => ['my_module/jquery_event']];

Finally, add this array to the $form

'#ajax' => [
        'callback' => '::myFormCallback',
        'event' => 'finishedinput',
0

This approach has both the advantage and disadvantage of being applied for all keyup-triggered AJAX events on any page where this script runs.

!function ($) {
  const originalMethod = Drupal.ajax.prototype.eventResponse,
        timeoutDelay   = 500;

  var timeoutId;

  // Override the default event handler
  Drupal.ajax.prototype.eventResponse = function (element, event) {
    const self = this;
    clearTimeout(timeoutId);

    if ('keyup' === this.event) {
      // Fire the original event handler with a delay
      timeoutId = setTimeout(function (element, event) {
        originalMethod.apply(self, [element, event]);
      }, timeoutDelay, element, event);
    }
    else {
      // Fire the original event handler immediately
      originalMethod.apply(this, [element, event]);
    }
  };
}(jQuery);
-1

This is the code I wrote:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt-br" lang="pt-br">
<head><title>Submit after typing finished</title>
<script language="javascript" type="text/javascript">
function DelayedSubmission() {
    var date = new Date();
    initial_time = date.getTime();
    if (typeof setInverval_Variable == 'undefined') {
            setInverval_Variable = setInterval(DelayedSubmission_Check, 50);
    } 
}
function DelayedSubmission_Check() {
    var date = new Date();
    check_time = date.getTime();
    var limit_ms=check_time-initial_time;
    if (limit_ms > 800) { //Change value in milliseconds
        alert("insert your function"); //Insert your function
        clearInterval(setInverval_Variable);
        delete setInverval_Variable;
    }
}

</script>
</head>
<body>

<input type="search" onkeyup="DelayedSubmission()" id="field_id" style="WIDTH: 100px; HEIGHT: 25px;" />

</body>
</html>
1
  • 3
    Welcome to Drupal Answers. You may improve your answer by writing a short paragraph explaining what the cide does, and how it will solve the OPs problem. Feb 22, 2015 at 4:57
-1

I also tried "beforeSend" without much luck. I then stumbled upon "beforeSubmit" and that does the trick for me. You can use this strategy to hook into other Drupal ajax prototype methods too (see /misc/ajax.js for all original methods):

(function($, Drupal) {
    var delayedTimeoutId;
    var delayInterval = 500;

    /**
     * Modify form values prior to form submission.
     */
    Drupal.ajax.prototype.original_beforeSubmit = Drupal.ajax.prototype.beforeSubmit;
    Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
        // Some console stuff for info purposes:
        if(window.console) {
            console.log('beforeSubmit args:');
            console.log(this); // contains stuff like PHP AJAX callback, triggering selector, etc.
            console.log(form_values); // the form data
            console.log(element); // the triggering element
            console.log(options); // ajax options
        }

        // If it is the triggering selector or callback I want to delay, then do the delay:
        if(this.selector == '#my-text-input-id' || this.callback == '_my_module_ajax_callback') {
            // Clear timeout if it exists;
            clearTimeout(delayedTimeoutId);
            // Start waiting:
            delayedTimeoutId = setTimeout(function(drupalAjax, form_values, element, options) {
                delayedTimeoutId = null;
                // Execute original beforeSubmit:
                drupalAjax.original_beforeSubmit(form_values, element, options);
            }, delayInterval, this, form_values, element, options)
        } else {
            // Continue with original beforeSubmit:
            this.original_beforeSubmit(form_values, element, options);
        }
    };
}(jQuery, Drupal));
3
  • Drupal.ajax.prototype.beforeSubmit is an empty function by default so this code doesn't actually do anything. It just invokes an empty with or without a delay.
    – tvanc
    Jul 18, 2017 at 21:41
  • RE: empty function - This is true. It is empty for now. But the Drupal API could change and it may no longer be empty. This is a safe override. Also, it currently doesn't do anything because it is up to the OP to insert whatever values they want to override.
    – jduhls
    Nov 15, 2017 at 15:09
  • Drupal even says it in the code to override this function: /** * Modify form values prior to form submission. */ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { // This function is left empty to make it simple to override for modules // that wish to add functionality here. };
    – jduhls
    Nov 15, 2017 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.