Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #7210 (new enhancement)

Opened 3 years ago

[PATCH] Support for an autocompleter that can be enabled/disabled

Reported by: victor73 Assigned to: thomas@fesch.at
Priority: normal Milestone:
Component: script.aculo.us Version:
Severity: normal Keywords:
Cc:

Description

Rationale:

I have found a use case that requires me to be able to activate and deactivate an autocompleter. For example, let's say that in a form, if a checkbox is checked, an input should have an autocompleter associated with it and when unchecked, the input is "freeform"...

Could not find support for this in scriptaculous, so contributing this code. Note, this is different from calling destroy(), as that would require the autocompleter to be recreated again... This code should also handle the indicator properly as well... A unit test is provided. It is known to fail in Safari 2.0.4 as is the base autocompleter.

The patch was provided as a separate .js file, but it shouldn't be too hard to integrate it into the main .js if desired...

The javascript library for the switchable autocompleter

/* Description: The scriptaculous Ajax.Autocompleter does not support or have a
mechanism to switch the control on and off. This code creates an extension of
the built in autocompleter with the new methods disable() and enable() to turn
the autocompleter on and off */
Ajax.SwitchableAutocompleter = Class.create();
Object.extend(Object.extend(Ajax.SwitchableAutocompleter.prototype, Autocompleter.Base.prototype), {
  initialize: function(element, update, url, options) {
    this.baseInitialize(element, update, options);
    this.options.asynchronous  = true;
    this.options.onComplete    = this.onComplete.bind(this);
    this.options.defaultParams = this.options.parameters || null;
    this.url                   = url;
    this.blurHandler           = new Array();
    this.keyHandler            = new Array();
  },
  disable: function() {
    this.hide();
    if (this.blurHandler.length==0 && this.keyHandler.length==0) {
        this._registerHandlers();
    }
    var ele=this.element;
    for (var i = 0; i < this.blurHandler.length; i++) {
      Event.stopObserving(ele, "blur", this.blurHandler[i]);
    }
    for (var i = 0; i < this.keyHandler.length; i++) {
      Event.stopObserving(ele, "keypress", this.keyHandler[i]);
    }
  },
  enable: function() {
    var ele=this.element;
    for (var i = 0; i < this.blurHandler.length; i++) {
      Event.observe(ele, "blur", this.blurHandler[i]);
    }
    for (var i = 0; i < this.keyHandler.length; i++) {
      Event.observe(ele, "keypress", this.keyHandler[i]);
    }
  },
  onComplete: function(request) {
    this.updateChoices(request.responseText);
  },
  getUpdatedChoices: function() {
    entry = encodeURIComponent(this.options.paramName) + '=' +
      encodeURIComponent(this.getToken());

    this.options.parameters = this.options.callback ?
      this.options.callback(this.element, entry) : entry;

    if(this.options.defaultParams)
      this.options.parameters += '&' + this.options.defaultParams;

    new Ajax.Request(this.url, this.options);
  },
  _registerHandlers: function() {
    if (!Event.observers) return;
    for (var i = 0; i < Event.observers.length; i++) {
      if (Event.observers[i] && Event.observers[i][0]==this.element) {
        var eventName=Event.observers[i][1];
        if (eventName=="blur") {
          this.blurHandler.push(Event.observers[i][2]);
        }
        if (eventName=="keypress" || eventName=="keydown") {
          this.keyHandler.push(Event.observers[i][2]);
        }
      }
    }
  }
});

The corresponding unit test:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>script.aculo.us Unit test file</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <script src="../../lib/prototype.js" type="text/javascript"></script>
  <script src="../../src/scriptaculous.js" type="text/javascript"></script>
  <script src="../../src/switchac.js" type="text/javascript"></script>
  <script src="../../src/unittest.js" type="text/javascript"></script>
  <link rel="stylesheet" href="../test.css" type="text/css" />
  <style>
    .selected { background-color: #888; }
  </style>
</head>
<body>
<h1>script.aculo.us Unit test file</h1>
<p>
  Tests for Ajax.SwitchableAutocompleter.
</p>

<!-- Log output -->
<div id="testlog"> </div>

<input id="sac_input" type="text" autocomplete="off" />
<div id="sac_update" style="display:none;border:1px solid black;background-color:white;position:relative;"></div>

<input id="sac_input_br" type="text" autocomplete="off" />
<div id="sac_update_br" style="display:none;border:1px solid black;background-color:white;position:relative;"></div>

<input id="actoken_input" type="text" autocomplete="off" />
<div id="actoken_update" style="display:none;border:1px solid black;background-color:white;position:relative;"></div>

<input id="dummy_element" type="text" autocomplete="off" />

<!-- Tests follow -->
<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[

  if (!("console" in window) || !("firebug" in console)) {
      var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
      "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

      window.console = {};
      for (var i = 0; i < names.length; ++i)
          window.console[names[i]] = function() {}
  }

  new Test.Unit.Runner({

    // Integration test, tests the entire cycle
    testSwitchableAutocompleter: function() { with(this) {
      var sac = new Ajax.SwitchableAutocompleter('sac_input','sac_update',
                    '_autocomplete_result.html',
        { method: 'get' }); //override so we can use a static for the result
      assertInstanceOf(Ajax.SwitchableAutocompleter, sac);

      // perform tests as thought this was a normal autocompleter
      console.info("Perform the basic autocompleter tests...");
      performNormalACTests(this);
      // Now disable the autocompleter and make sure it's stopped. No funny business...
      console.info("Disabling the autocompleter...");
      sac.disable();
      wait(1000, function() { with(this) {
          assertNotVisible('sac_update');
      }});
      // Clear the contents of the input by simulating backspaces...
      clearWithBackspaces('sac_input');
      Event.simulateKeys('sac_input','some text');
      assertEqual('some text',$('sac_input').value);
      wait(1000, function() { with(this) {
          assertNotVisible('sac_update');
      }});
      // Okay, now that we've satisfied ourselves that the autocompleter behavior is off
      // Let's turn it back on and ensure everything is back to normal again...
      clearWithBackspaces('sac_input');
      console.info("Re-enabling the autocompleter...");
      sac.enable();
      console.info("Re-perform the basic autocompleter tests...");
      performNormalACTests(this);
    }},
    testTokenizing: function() { with(this) {
      var actoken = new Ajax.SwitchableAutocompleter('actoken_input',
                        'sac_update','_autocomplete_result.html',
        { tokens:',', method: 'get' });
      assertInstanceOf(Ajax.SwitchableAutocompleter, actoken);

      Event.simulateKeys('actoken_input','abc');

      wait(1000, function() { with(this) {
        Event.simulateKey('actoken_input','keypress',{keyCode:Event.KEY_TAB});
        assertEqual('test1',$('actoken_input').value);
        Event.simulateKeys('actoken_input',',abc');
        wait(1000, function() { with(this) {
          Event.simulateKey('actoken_input','keypress',{keyCode:Event.KEY_DOWN});
          Event.simulateKey('actoken_input','keypress',{keyCode:Event.KEY_TAB});
          assertEqual('test1,test2',$('actoken_input').value);
        }});
      }});
    }}

  });

  function clearWithBackspaces(inputName) {
      console.debug("In clearWithBackspaces.");
      while ($('sac_input').value != "") {
          Event.simulateKey('sac_input', 'keypress', {keyCode:Event.KEY_BACKSPACE});
          console.count('Simulated backspaces: ');
      }
  }

  function performNormalACTests(t) {
      console.debug("In performNormalACTests.");
      // box not visible
      t.assertNotVisible('sac_update');

      // focus, but box not visible
      Event.simulateMouse('sac_input', 'click');
      t.assertNotVisible('sac_update');

      var inputText = "abcdefg";
      Event.simulateKeys('sac_input',inputText);
      t.assertEqual(inputText, $('sac_input').value);

      // check box popping up on input
      t.wait(1000, function() { with(t) {
        assertVisible('sac_update');
        assertEqual('test1', $('sac_update').firstChild.firstChild.innerHTML);
        assertEqual('test2', $('sac_update').firstChild.firstChild.nextSibling.innerHTML);

        // first entry should be selected
        assert(Element.hasClassName($('sac_update').firstChild.firstChild, 'selected'),'Selected item should have a className of
: selected');

        Event.simulateKey('sac_input','keypress',{keyCode:Event.KEY_DOWN});

        // second entry should be selected
        assert(!Element.hasClassName($('sac_update').firstChild.firstChild),'Item shouldn\'t have a className of: selected');
        assert(Element.hasClassName($('sac_update').firstChild.firstChild.nextSibling, 'selected'),'Second entry should have a c
lassName of: selected');

        wait(1000, function() { with(t) {
           assertVisible('sac_update');
        }});

        // check selecting with <TAB>
        Event.simulateKey('sac_input','keypress',{keyCode:Event.KEY_TAB});
        assertEqual('test2',$('sac_input').value);

        // check box going away
        wait(500, function() { with(t) {
          assertNotVisible('sac_update');

          // check selecting with mouse click
          Event.simulateKeys('sac_input','3');
          assertEqual('test23', $('sac_input').value);
          wait(1000, function() { with(t) {
            assertVisible('sac_update');
            Event.simulateMouse($('sac_update').firstChild.childNodes[4],'click');

            wait(1000, function() { with(t) {
              // tests if removal of 'informal' nodes and HTML escaping works
              assertEqual('(GET <ME> INSTEAD)',$('sac_input').value);
              assertNotVisible('sac_update');

                // check cancelling with <ESC>
                Event.simulateKeys('sac_input','abcdefg');

                wait(1000, function() { with(t) {
                  assertVisible('sac_update');
                  assertEqual('(GET <ME> INSTEAD)abcdefg', $('sac_input').value);

                  Event.simulateKey('sac_input','keypress',{keyCode:Event.KEY_DOWN});
                  Event.simulateKey('sac_input','keypress',{keyCode:Event.KEY_ESC});

                  assertEqual('(GET <ME> INSTEAD)abcdefg', $('sac_input').value);
                }});
            }});
          }});
        }});
      }});
  }
// ]]>
</script>
</body>
</html>