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 | |
---|
44 | Autocompleter.Combobox = Class.create(); |
---|
45 | Object.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 | /* |
---|
69 | The one issue I had (and hence the extra Event.observers and this.overSelectDefault and this.clickedSelectDefault) |
---|
70 | was 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' |
---|
72 | element, in which case you don't want to hide the dropdown but show it! |
---|
73 | |
---|
74 | I first tried to see if the events were called in any particular order (deterministic). I didn't |
---|
75 | think this would work but I thought it was worth a shot. The answer depends on the browser, but |
---|
76 | most are non-deterministic. Because they aren't called in a pre-defined order, that also meant |
---|
77 | that you can't set a state flag in one (like 'I just clicked the selectDefault element!'), and expect |
---|
78 | to check it in the other event ('hide me IF the selectDefault was not clicked!'). |
---|
79 | |
---|
80 | The 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 |
---|
82 | event, which is given to you by prototype's Event.element), or the X,Y coordinates of the event to |
---|
83 | see if I could compare that to the location of the selectDefault element (prototype's Position.within). |
---|
84 | |
---|
85 | Neither of those worked. Firefox has an attribute for events that tell you what element you were on |
---|
86 | when the event was triggered. IE supposedly has something similar, but opera and safari do not. So |
---|
87 | that won't work. And, the onBlur event doesn't set the X and Y coordinates (or any of the numerous |
---|
88 | position attributes) in either firefox or safari. doah! |
---|
89 | |
---|
90 | So the end solution was to set a flag on a mouseover and mouseout on the selectDefault element. |
---|
91 | The idea is that this will occur before a click and there will be time to set the flag before |
---|
92 | the 'onClick' and 'onBlur' events are triggered simultaneously. This works really well, but leaves one |
---|
93 | last catch: sometimes when you click the selectDefault you *want* the default menu to disappear. Hence |
---|
94 | the 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 | }); |
---|