root/public/javascripts/combo2.js

Revision 1, 8.4 kB (checked in by falcon, 17 years ago)

Version one -> initial work from the laptop.

Line 
1/*
2        The combobox control.  This is very simililar to the local array
3        autocompleter but with the addition that if a specified control
4        (like an image) is clicked it will show a default list.
5        Otherwise it behaves exactly like the local array autocompleter.
6
7        The constructor is the same as the local array autocompleter.
8       
9        To specify a different array for the 'show default' control,
10        add the option 'defaultArray', and pass to it a valid javascript array.
11        This control won't work so well if the two arrays you pass in are
12        wildly different.  Generally you need to have the defaultArray be a
13        subset of the main array.
14       
15        I've commented in-line to help explain some of the choices.
16       
17        One last thing:  a 'super' would be *really* nice for stuff like this.
18        There is a lot of dup code because I can't call super; or super.onBlur().
19        I guess I could name the methods something different, and we can do that
20        to keep the code DRY, but I left the dupe lines in to make it clear as to
21        what is going on.
22       
23        An example (in html) is:
24        <script type="text/javascript">
25                //*horrible* baby names (first names!  try and guess which ones are for
26                // boys and girls.  ugh)
27                var all_options = ['Ambrosia','Attica','Autumn Night','Avellana','Bow Hunter',
28                'Brielle','Brooklynn','Cash','Cinsere','Crisiant','Cyndee','Delphine','Donte',
29                'Drudwen','Dusk','Electra','Emmaleigh','Enfys','Enobi','Eurwen','Faerin',
30                'Faleiry','Francessca','Franka','Fritha','Gage','Gaiety','Gennavieve','Gibson'];
31                // the worst.  Ok, couldn't cull it down enough, so any 5
32                var default_options = ['Avellana','Enobi','Eurwen','Faleiry','Gage'];
33
34        </script>
35        TEST:<input type="text" id="name-field"/>
36        <image id='name-arrow' src="/images/combo_box_arrow.png"/>
37        <div class="auto_complete" id="lookup_auto_complete"></div>
38        <%= javascript_tag("new Autocompleter.Combobox('name-field',
39        'lookup_auto_complete', 'name-arrow',
40        all_options,{partialChars:1, ignoreCase:true, fullSearch:true, frequency:0.1,
41                                                         choices:6, defaultArray:default_options, tokens: ',' });") %>
42*/
43
44Autocompleter.Combobox = Class.create();
45Object.extend(Object.extend(Autocompleter.Combobox.prototype, Autocompleter.Local.prototype), {
46
47        initialize: function(element, update, selectDefault, array, options) {
48                this.baseInitialize(element, update, options);
49                this.selectDefault = selectDefault;
50                //this keeps track of whether or not the click is currently over the 'selectDefault'.  See
51                // below for more detail.
52                this.overSelectDefault = false;
53                this.clickedSelectDefault = 0;
54               
55                this.options.array           = this.options.array || array;
56                this.options.defaultArray    = this.options.defaultArray || this.options.array;
57                this.options.defaultChoices  = this.options.defaultChoices || this.options.defaultArray.length;
58               
59                Event.observe(this.selectDefault, 'click', this.click.bindAsEventListener(this));               
60
61                // these events are all in place to coordinate between te onBlur event for the input
62                // field and the click event for the selectDefault.  See below for more detail.
63    Event.observe(this.selectDefault, "mouseover", this.onMouseover.bindAsEventListener(this));
64    Event.observe(this.selectDefault, "mouseout", this.onMouseout.bindAsEventListener(this));
65    Event.observe(this.element, "keypress", this.resetClick.bindAsEventListener(this));
66        },
67
68/*
69The one issue I had (and hence the extra Event.observers and this.overSelectDefault and this.clickedSelectDefault)
70was that if you had the input field highlighted and clicked _anywhere_ outside the input field, the
71'onBlur' method would be called.  This makes sense except in the case where you hit the 'selectDefault'
72element, in which case you don't want to hide the dropdown but show it!
73
74I first tried to see if the events were called in any particular order (deterministic).  I didn't
75think this would work but I thought it was worth a shot.  The answer depends on the browser, but
76most are non-deterministic.  Because they aren't called in a pre-defined order, that also meant
77that you can't set a state flag in one (like 'I just clicked the selectDefault element!'), and expect
78to check it in the other event ('hide me IF the selectDefault was not clicked!').
79
80The other hope was that the event could give me information about either which element was
81*active* when the event was triggered (this is different than asking which element is tied to the
82event, which is given to you by prototype's Event.element), or the X,Y coordinates of the event to
83see if I could compare that to the location of the selectDefault element (prototype's Position.within).
84
85Neither of those worked.  Firefox has an attribute for events that tell you what element you were on
86when the event was triggered.  IE supposedly has something similar, but opera and safari do not.  So
87that won't work.  And, the onBlur event doesn't set the X and Y coordinates (or any of the numerous
88position attributes) in either firefox or safari.  doah!
89
90So the end solution was to set a flag on a mouseover and mouseout on the selectDefault element.
91The idea is that this will occur before a click and there will be time to set the flag before
92the 'onClick' and 'onBlur' events are triggered simultaneously.  This works really well, but leaves one
93last catch: sometimes when you click the selectDefault you *want* the default menu to disappear.  Hence
94the reason to keep track of the state of the selectDefault element (previously clicked or not clicked).
95*/
96
97        onMouseover: function(event){
98                this.overSelectDefault = true;
99        },
100
101        onMouseout: function(event){
102                this.overSelectDefault = false;
103        },
104       
105        resetClick :function(event) {
106                this.clickedSelectDefault = 0;
107        },
108       
109        onBlur: function(event, force) {
110                if (this.overSelectDefault && !force)
111                {
112                        //no need to blur
113                        return;
114                }
115                // this is where you can call super.onBlur();
116          setTimeout(this.hide.bind(this), 250);
117          this.hasFocus = false;
118          this.active = false;
119          this.clickedSelectDefault = false;
120        },
121
122        click: function(event) {
123                this.clickedSelectDefault = Math.abs(this.clickedSelectDefault - 1);
124                this.element.focus();
125                this.changed = false;
126                this.hasFocus = true;
127                this.getAllChoices();
128                if ( Element.getStyle(this.update, 'display') != 'none' && this.clickedSelectDefault != 1) {
129                        this.onBlur(event, true);
130                }
131        },
132
133  // this is *almost* exactly the same as Autocompleter.local.selector method
134  // there can definitely be some refactoring done in the control.js file
135  // to keep it DRY
136  // What I changed was that if the user hits the defaultSelect element, I
137  // *always* want to show all the elements in the defaultSelect 'dropdown'.
138  // that way there is some consistency about what is shown when the user
139  // hits that element. 
140  // but to recognize that they might have already typed something in the
141  // input field, this function (like the one in Autocomplete.local) highlights
142  // the matching chars, if there are any.
143        getAllChoices: function(e) {
144                  var ret       = []; // Beginning matches
145      var partial   = []; // Inside matches
146      var entry     = this.getToken();
147      var count     = 0;
148      for (var i = 0; i < this.options.defaultArray.length && 
149        ret.length < this.options.defaultChoices; i++) { 
150        var elem = this.options.defaultArray[i];
151        var foundPos = this.options.ignoreCase ? 
152          elem.toLowerCase().indexOf(entry.toLowerCase()) : 
153          elem.indexOf(entry);
154                                if (entry == "" || foundPos == -1)
155                                {
156                                        ret.push("<li>" + elem + "</li>");
157                                        continue;                                       
158                                }
159        while (foundPos != -1) {
160          if (foundPos == 0 && elem.length != entry.length) { 
161            ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
162              elem.substr(entry.length) + "</li>");
163            break;
164          } else if (entry.length >= this.options.partialChars && 
165            this.options.partialSearch && foundPos != -1) {
166            if (this.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
167              partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
168                elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
169                foundPos + entry.length) + "</li>");
170              break;
171            }
172          }
173
174          foundPos = this.options.ignoreCase ? 
175            elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
176            elem.indexOf(entry, foundPos + 1);
177
178        }
179      }
180      if (partial.length)
181      ret = ret.concat(partial.slice(0, this.options.defaultChoices - ret.length))
182                this.updateChoices("<ul>" + ret.join('') + "</ul>");
183        }
184});
Note: See TracBrowser for help on using the browser.