/[jquery]/tag_complete/jquery-tac.js
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /tag_complete/jquery-tac.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 58 - (hide annotations)
Sun Aug 20 23:23:29 2006 UTC (17 years, 8 months ago) by dpavlin
File MIME type: application/javascript
File size: 7074 byte(s)
additional tweaking to prevent unneeded re-parsing, current_tag now returns '' if
there is no need for update
1 dpavlin 54 /*
2     tag auto-completer written with jquery (prototype style)
3    
4     2006-08-20 Dobrica Pavlinusic <dpavlin@rot13.org>
5     */
6    
7     function tac(
8     tag_class, // tags are elements with this class
9     tags_form_id, // id of tags edit form
10     foo
11     ) {
12    
13     // all indexed by tag name
14     this.t = {
15     obj: new Array(), // html objects
16     exists: new Array()
17     };
18    
19     this.all_tags = new Array();
20     this.suggested = new Array();
21    
22     this.entered = { // array of entered tags
23     ordered: new Array(), // tags entered in original order
24     tag: new Array() // mapping from tag name to order number
25     };
26    
27     this.current_selected = null; // current selected suggestion
28    
29     this.tags_selector = '.tag';
30     if (tag_class) this.tags_selector = '.'+tag_class;
31     this.tags_form = '#tags_form';
32     if (tags_form_id) this.tags_form = '#'+tags_form_id;
33     this.suggest_selector = '#suggest';
34     this.tags_input = '#tags';
35    
36 dpavlin 58 // all tags from previous event
37 dpavlin 54 this.last_tags = '';
38 dpavlin 58 // just last tag
39     this.last_tag = '';
40 dpavlin 54
41     // parse all tags from html
42     var obj = this;
43     $( this.tags_selector ).each( function(i) {
44     var n = this.firstChild.nodeValue;
45    
46     obj.t.obj[n] = this;
47     obj.t.exists[n] = true;
48     obj.all_tags.push( n );
49    
50     this.onclick = function() {
51     return obj.tag( n );
52     }
53     this.href = '#'+i; // FIXME debug
54     });
55     $.log.info( 'found ' + this.all_tags.length + ' tags' );
56    
57     $.log.info( 'hook onchange to '+this.tags_form );
58     $( this.tags_form ).keyup( function(e) {
59    
60     $.log.debug('keyup: '+e.keyCode);
61    
62 dpavlin 58 var ret = true;
63    
64 dpavlin 54 switch (e.keyCode) {
65     case 38: // up
66     e.preventDefault();
67     obj.move_suggested( -1 );
68     return false;
69     case 40: // down
70     e.preventDefault();
71     obj.move_suggested( +1 );
72     return false;
73     case 9: // tab
74 dpavlin 56 obj.focus();
75     obj.take_suggested();
76     return false;
77 dpavlin 54 case 13: // return
78     if (obj.current_selected != null) obj.focus();
79 dpavlin 56 return true;
80 dpavlin 54 case 8: // backspace
81     case 46: // del
82 dpavlin 58 ret = false;
83     case 37: // left
84     case 39: // right
85     return true;
86 dpavlin 54 }
87    
88    
89     var t = obj.current_tag();
90    
91 dpavlin 58 if (t == '') return false;
92    
93 dpavlin 54 $.log.debug('tag: ' + t + ' ['+t.length+']');
94    
95     obj.clean_suggested();
96 dpavlin 58 obj.suggest(t);
97 dpavlin 54
98 dpavlin 58 return ret;
99 dpavlin 54
100     }).submit( function() {
101     $.log.debug('submit');
102     return obj.take_suggested();
103     }).blur( function() {
104     $.log.debug('blur');
105     return obj.take_suggested();
106     });
107    
108     $.log.toggle();
109    
110     this.focus();
111     }
112    
113     tac.prototype.suggest = function( partial ) {
114    
115     $.log.info('suggest to '+partial);
116    
117     var len = partial.length;
118     var suggest = '';
119     this.suggested = new Array();
120    
121     for(var i = 0; i < this.all_tags.length; i++) {
122     var t = this.all_tags[i];
123     if ( t.substr(0, len) == partial ) {
124     $.log.debug('suggested: ' + t );
125    
126     jQuery.className.add( this.t.obj[t], 'suggested' );
127    
128 dpavlin 55 suggest += '<a href="#"';
129 dpavlin 54 if (this.suggested.length == 0) suggest += ' class="suggested" ';
130 dpavlin 55 suggest += '">' + t + '</a> ';
131 dpavlin 54 this.suggested.push(t);
132     }
133     }
134    
135     $(this.suggest_selector).html( suggest );
136 dpavlin 55 var obj = this;
137     $(this.suggest_selector+' a').click( function() {
138     var t = this.firstChild.nodeValue;
139     $.log.debug('click add: '+ t);
140     obj.clean_suggested();
141 dpavlin 57 obj.add_tag( t );
142 dpavlin 55 });
143 dpavlin 54 this.current_suggested = 0;
144     $.log.info('suggested ' + this.suggested.length + ' tags');
145    
146    
147     }
148    
149     tac.prototype.clean_suggested = function() {
150     for(var i = 0; i < this.suggested.length; i++) {
151     var t = this.suggested[i];
152     jQuery.className.remove( this.t.obj[t], 'suggested' );
153     }
154    
155     var c = this.current_suggested;
156     if (c == null) return c;
157    
158     $(this.suggest_selector).html('');
159     this.current_suggested = null;
160    
161     return c;
162     }
163    
164     tac.prototype.take_suggested = function() {
165     var c = this.current_suggested;
166     if (c == null) {
167     $.log.debug('no current_select, return true');
168     return true;
169     }
170    
171     var s = $(this.suggest_selector+' a:nth('+c+')').html();
172     if (s == null) {
173     $.log.debug('no suggest, return true');
174     return true;
175     }
176    
177     var i = this.suggested[c].i;
178 dpavlin 55 $.log.debug('take_suggested: '+s);
179 dpavlin 54 this.add_tag( s );
180     return false;
181     }
182    
183     tac.prototype.move_suggested = function( where ) {
184     var c = this.current_suggested;
185     if (c == null) {
186     $.log.error('this.current_suggested is null');
187     return;
188     }
189     var to = c + where;
190     $.log.info('move_suggested('+where+') '+c+' -> '+to);
191     if (to < 0 || to >= this.suggested.length) {
192     $.log.error('move to invalid element '+to);
193     return;
194     }
195     var s = this.suggest_selector+' a:nth('+c+')';
196     $( s ).removeClass('suggested');
197     $.log.debug('remove suggested from '+s);
198     s = this.suggest_selector+' a:nth('+to+')';
199     $( s ).addClass('suggested');
200     $.log.debug('add suggested to '+s);
201     this.current_suggested = to;
202     }
203    
204     /*
205     tag operations
206     */
207    
208     tac.prototype.add_tag = function( t ) {
209     $.log.info('add '+t);
210     this.focus();
211    
212     jQuery.className.add( this.t.obj[t], 'entered' );
213    
214     var last = this.entered.ordered.length;
215     this.entered.ordered[ last ] = t;
216     this.entered.tag[t] = last;
217    
218     this.clean_suggested();
219    
220     // strip last tag and add new one
221     $(this.tags_input).val(
222     $(this.tags_input).val().replace(
223     /[^ ]*$/, t + ' '
224     )
225     );
226     return false;
227     }
228    
229     tac.prototype.remove_tag = function( t ) {
230     $.log.info('remove '+t);
231    
232     this.focus();
233    
234     var o = this.entered.tag[t];
235     if (o == null) {
236     $.log.error('remove non-entered tag '+t);
237     return false;
238     }
239    
240     jQuery.className.remove( this.t.obj[t], 'entered' );
241    
242     // remove suggested tag and rebuild tags
243    
244     this.entered.ordered.splice(o,1);
245     this.entered.tag[t] = null;
246    
247     for (var i = o; i < this.entered.ordered.length; i++) {
248     this.entered.tag[ this.entered.ordered[i] ] = i;
249     }
250    
251     this.clean_suggested();
252    
253 dpavlin 57 var tags = this.entered.ordered.join(' ');
254     if (tags.length) tags += ' ';
255 dpavlin 54 $.log.debug('removed '+o+' left: '+tags);
256     $(this.tags_input).val( tags );
257     return false;
258     }
259    
260     tac.prototype.tag = function( t ) {
261     if (this.entered.tag[t] != null) {
262     this.remove_tag( t );
263     } else {
264     this.add_tag( t );
265     }
266     }
267    
268     /*
269     parse current string into tags
270     */
271    
272     tac.prototype.parse = function() {
273     $.log.info('re-parse tags');
274    
275     var t = $(this.tags_input).val().replace(/^ */,'').replace(/ *$/,'').split(/ /);
276    
277 dpavlin 58 if (this.entered.ordered.length) $('.entered').removeClass('entered');
278     this.clean_suggested();
279    
280 dpavlin 54 this.entered = {
281     ordered: new Array(),
282     tag: new Array()
283     };
284    
285     var debug = '';
286     for (var i = 0; i < t.length; i++) {
287     var tag = t[i];
288     if (this.t.exists[ tag ] != null) {
289     this.entered.tag[tag] = this.entered.ordered.length;
290     this.entered.ordered.push( tag );
291     jQuery.className.add( this.t.obj[ tag ], 'entered' );
292     debug += tag + ' '
293     } else {
294     debug += '{'+tag+'} ';
295     }
296     }
297     $.log.debug('parsed '+debug+' to '+this.entered.ordered.join(','));
298     }
299    
300     tac.prototype.current_tag = function() {
301     var tags = $(this.tags_input).val();
302     if (tags != this.last_tags) {
303     this.last_tags = tags;
304     this.parse();
305     }
306 dpavlin 58 var last_tag = tags.replace(/^([^ ][^ ]* )*/, '');
307     if (last_tag != this.last_tag) {
308     this.last_tag = last_tag;
309     return last_tag;
310     } else {
311     this.parse();
312     return '';
313     }
314 dpavlin 54 }
315    
316     tac.prototype.focus = function() {
317     // $(this.tags_input).focus() doesn't work!
318     document.getElementById('tags').focus();
319     }
320    
321     // example usage
322     var tags;
323     $(document).ready( function() {
324     tags = new tac();
325     });
326    

  ViewVC Help
Powered by ViewVC 1.1.26