<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Musings of a Trained Monkey: Typo Administration: article tagging V...</title>
    <link>http://www.stevelongdo.com/articles/2006/03/19/typo-administration-article-tagging-v</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description></description>
    <item>
      <title>Typo Administration: article tagging V...</title>
      <description>Originally we pursued using the &lt;a href="http://wiki.script.aculo.us/scriptaculous/show/Autocompleter.Local"&gt;Autocompleter.Local&lt;/a&gt; from the&lt;a href="http://script.aculo.us/"&gt;script.aculo.us&lt;/a&gt; library.  This had some promising results.  However I wanted to be able to do a listing of all of the tags in a scrollable field not just the matches to what I typed.  For example: &lt;cite&gt;I typed in 'r' and matched 'ruby' but I  can also see 'sql' which might also be a good tag to add to the posting I am working on.&lt;/cite&gt;&lt;br /&gt;&lt;br /&gt;
This led me to extend &lt;a href="http://wiki.script.aculo.us/scriptaculous/show/Autocompleter.Base"&gt;Autocompleter.Base&lt;/a&gt; instead and add the desired functionality myself.  Adding scrolling proved a bit tricky, mainly because the offsetHeight property of the element tags always returns zero.  I think this is because it is inside of an absolutely positioned DIV, but I am not a &lt;a href="http://www.meyerweb.com/eric/css/"&gt;CSS guru&lt;/a&gt; (believe it or not:-P).  I created an AdminAutotag class that is a replica of the  &lt;a href="http://wiki.script.aculo.us/scriptaculous/show/Autocompleter.Local"&gt;Autocompleter.Local&lt;/a&gt; class.  The selector function needs to be radically altered to support showing all tags and not just matches.  Also in this setup it wouldn't make sense to support partial matches so we will remove that.  We can still handle case sensitivity though.    Since we want this to eventually to end up in &lt;a href="http://typosphere.org"&gt;Typo&lt;/a&gt;, instead of putting all of the JavaScript directly on the _form.rhtml partial, lets move it instead to the typo.js file in /public.  
&lt;div class="typocode"&gt;&lt;div class="codetitle"&gt;public/javascript/typo.js&lt;/div&gt;&lt;table class="typocode_linenumber"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="lineno"&gt;
1&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
4&lt;br /&gt;
5&lt;br /&gt;
6&lt;br /&gt;
7&lt;br /&gt;
8&lt;br /&gt;
9&lt;br /&gt;
10&lt;br /&gt;
11&lt;br /&gt;
12&lt;br /&gt;
13&lt;br /&gt;
14&lt;br /&gt;
15&lt;br /&gt;
16&lt;br /&gt;
17&lt;br /&gt;
18&lt;br /&gt;
19&lt;br /&gt;
20&lt;br /&gt;
21&lt;br /&gt;
22&lt;br /&gt;
23&lt;br /&gt;
24&lt;br /&gt;
25&lt;br /&gt;
26&lt;br /&gt;
27&lt;br /&gt;
28&lt;br /&gt;
29&lt;br /&gt;
30&lt;br /&gt;
31&lt;br /&gt;
32&lt;br /&gt;
33&lt;br /&gt;
34&lt;br /&gt;
35&lt;br /&gt;
36&lt;br /&gt;
37&lt;br /&gt;
38&lt;br /&gt;
39&lt;br /&gt;
40&lt;br /&gt;
41&lt;br /&gt;
42&lt;br /&gt;
43&lt;br /&gt;
44&lt;br /&gt;
45&lt;br /&gt;
46&lt;br /&gt;
47&lt;br /&gt;
48&lt;br /&gt;
49&lt;br /&gt;
50&lt;br /&gt;
51&lt;br /&gt;
52&lt;br /&gt;
53&lt;br /&gt;
54&lt;br /&gt;
&lt;/td&gt;&lt;td width="97%"&gt;&lt;pre&gt;&lt;code class="javascript2"&gt;AdminAutotag = Class.create&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
AdminAutotag.&lt;span class="global"&gt;prototype&lt;/span&gt; = Object.extend&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;new&lt;/span&gt; Autocompleter.Base&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;, {
	initialize: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="global"&gt;element&lt;/span&gt;, update, array, options&lt;span class="bracket"&gt;)&lt;/span&gt; {
	  &lt;span class="keyword"&gt;this&lt;/span&gt;.baseInitialize&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="global"&gt;element&lt;/span&gt;, update, options&lt;span class="bracket"&gt;)&lt;/span&gt;;
	  &lt;span class="keyword"&gt;this&lt;/span&gt;.options.array = array;
	},
  getUpdatedChoices: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; {
    &lt;span class="keyword"&gt;this&lt;/span&gt;.updateChoices&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.options.selector&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
  },
  setOptions: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;options&lt;span class="bracket"&gt;)&lt;/span&gt; {
    &lt;span class="keyword"&gt;this&lt;/span&gt;.options = Object.extend&lt;span class="bracket"&gt;(&lt;/span&gt;{
      ignoreCase: &lt;span class="keyword"&gt;true&lt;/span&gt;,
	&lt;span class="comment"&gt;/* start selector */&lt;/span&gt;

      selector: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;instance&lt;span class="bracket"&gt;)&lt;/span&gt; {
	  &lt;span class="keyword"&gt;var&lt;/span&gt; ret       = []; &lt;span class="comment"&gt;// Beginning matches
&lt;/span&gt;	  &lt;span class="keyword"&gt;var&lt;/span&gt; entry     = instance.getToken&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
	  &lt;span class="keyword"&gt;var&lt;/span&gt; firstMatch= -1;
	  &lt;span class="keyword"&gt;for&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; i = 0; i &amp;lt; instance.options.array.length; i++&lt;span class="bracket"&gt;)&lt;/span&gt; { 
		&lt;span class="keyword"&gt;var&lt;/span&gt; wasMatch = &lt;span class="keyword"&gt;false&lt;/span&gt;;
	    &lt;span class="keyword"&gt;var&lt;/span&gt; elem = instance.options.array[i];
	    &lt;span class="keyword"&gt;var&lt;/span&gt; foundPos = instance.options.ignoreCase ? 
	      elem.toLowerCase&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;.indexOf&lt;span class="bracket"&gt;(&lt;/span&gt;entry.toLowerCase&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; : elem.indexOf&lt;span class="bracket"&gt;(&lt;/span&gt;entry&lt;span class="bracket"&gt;)&lt;/span&gt;;

	    &lt;span class="keyword"&gt;while&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;foundPos != -1&lt;span class="bracket"&gt;)&lt;/span&gt; {
	      &lt;span class="keyword"&gt;if&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;foundPos == 0 &amp;amp;&amp;amp; elem.length != entry.length&lt;span class="bracket"&gt;)&lt;/span&gt; { 
			wasMatch = &lt;span class="keyword"&gt;true&lt;/span&gt;;
			&lt;span class="keyword"&gt;if&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;firstMatch &amp;lt; 0&lt;span class="bracket"&gt;)&lt;/span&gt;{
			  firstMatch=i;
			  instance.index=i;
	          ret.push&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="string"&gt;"&amp;lt;li class=\"&lt;/span&gt;selected\&lt;span class="string"&gt;"&amp;gt;&amp;lt;strong&amp;gt;"&lt;/span&gt; + elem.substr&lt;span class="bracket"&gt;(&lt;/span&gt;0, entry.length&lt;span class="bracket"&gt;)&lt;/span&gt; + &lt;span class="string"&gt;"&amp;lt;/strong&amp;gt;"&lt;/span&gt; + 
	              elem.substr&lt;span class="bracket"&gt;(&lt;/span&gt;entry.length&lt;span class="bracket"&gt;)&lt;/span&gt; + &lt;span class="string"&gt;"&amp;lt;/li&amp;gt;"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
			} &lt;span class="keyword"&gt;else&lt;/span&gt; {
		        ret.push&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="string"&gt;"&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;"&lt;/span&gt; + elem.substr&lt;span class="bracket"&gt;(&lt;/span&gt;0, entry.length&lt;span class="bracket"&gt;)&lt;/span&gt; + &lt;span class="string"&gt;"&amp;lt;/strong&amp;gt;"&lt;/span&gt; + 
		          elem.substr&lt;span class="bracket"&gt;(&lt;/span&gt;entry.length&lt;span class="bracket"&gt;)&lt;/span&gt; + &lt;span class="string"&gt;"&amp;lt;/li&amp;gt;"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
			}
	        &lt;span class="keyword"&gt;break&lt;/span&gt;;
	      }      

	      foundPos = instance.options.ignoreCase ? 
	        elem.toLowerCase&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;.indexOf&lt;span class="bracket"&gt;(&lt;/span&gt;entry.toLowerCase&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;, foundPos + 1&lt;span class="bracket"&gt;)&lt;/span&gt; : 
	        elem.indexOf&lt;span class="bracket"&gt;(&lt;/span&gt;entry, foundPos + 1&lt;span class="bracket"&gt;)&lt;/span&gt;;
	    }
		&lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;wasMatch==&lt;span class="keyword"&gt;false&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;{
	    	ret.push&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="string"&gt;"&amp;lt;li&amp;gt;"&lt;/span&gt; + elem + &lt;span class="string"&gt;"&amp;lt;/li&amp;gt;"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
		}
	  }
	  instance.&lt;span class="global"&gt;element&lt;/span&gt;.autocompleteIndex=firstMatch;
	  instance.index=firstMatch;
	  &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="string"&gt;"&amp;lt;ul&amp;gt;"&lt;/span&gt; + ret.join&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="string"&gt;''&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; + &lt;span class="string"&gt;"&amp;lt;/ul&amp;gt;"&lt;/span&gt;;
	}
	&lt;span class="comment"&gt;//end

&lt;/span&gt;    }, options || {}&lt;span class="bracket"&gt;)&lt;/span&gt;;
  }
}&lt;span class="bracket"&gt;)&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

Besides removing the partial matching code, the primary difference here is that we are pushing out elements whether or not they match (#43-#45). Notice also lines 47 and 48 that set our first match.  This how we will keep track of where to scroll to.  In fact how are we going to handle that? We will have to control the rendering of the component so we will need to override that method. Our render function will accept a parameter, unlike the base version.  This is to handle the &lt;a href="http://www.stevelongdo.com/articles/2006/03/19/typo-admin-tagging-and-magnolia-update"&gt;auto-scrolling via mouse pointer capability of the DIV I mentioned before&lt;/a&gt;.  This will allow us to selectively allow scrolling, i.e. not onMouseover events.
Besides removing the partial matching code, the primary difference here is that we are pushing out elements whether or not they match (#43-#45). Notice also lines 47 and 48 that set our first match.  This how we will keep track of where to scroll to.  In fact how are we going to handle that? We will have to control the rendering of the component so we will need to override that method. Our render function will accept a parameter, unlike the base version.  This is to handle the &lt;a href="http://www.stevelongdo.com/articles/2006/03/19/typo-admin-tagging-and-magnolia-update"&gt;auto-scrolling via mouse pointer capability of the DIV I mentioned before&lt;/a&gt;.  This will allow us to selectively allow scrolling, i.e. not onMouseover events.
&lt;div class="typocode"&gt;&lt;div class="codetitle"&gt;Inside AdminAutotag.prototype&lt;/div&gt;&lt;table class="typocode_linenumber"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="lineno"&gt;
1&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
4&lt;br /&gt;
5&lt;br /&gt;
6&lt;br /&gt;
7&lt;br /&gt;
8&lt;br /&gt;
9&lt;br /&gt;
10&lt;br /&gt;
11&lt;br /&gt;
12&lt;br /&gt;
13&lt;br /&gt;
14&lt;br /&gt;
15&lt;br /&gt;
16&lt;br /&gt;
17&lt;br /&gt;
18&lt;br /&gt;
19&lt;br /&gt;
20&lt;br /&gt;
21&lt;br /&gt;
22&lt;br /&gt;
&lt;/td&gt;&lt;td width="100%"&gt;&lt;pre&gt;&lt;code class="javascript2"&gt;  render: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;scrollActive&lt;span class="bracket"&gt;)&lt;/span&gt; {
	&lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.index==0 &amp;amp;&amp;amp; &lt;span class="keyword"&gt;this&lt;/span&gt;.options.picked!=0&lt;span class="bracket"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;this&lt;/span&gt;.index = &lt;span class="keyword"&gt;this&lt;/span&gt;.&lt;span class="global"&gt;element&lt;/span&gt;.autocompleteIndex;
    &lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.entryCount &amp;gt; 0&lt;span class="bracket"&gt;)&lt;/span&gt; {
      &lt;span class="keyword"&gt;for&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; i = 0; i &amp;lt; &lt;span class="keyword"&gt;this&lt;/span&gt;.entryCount; i++&lt;span class="bracket"&gt;)&lt;/span&gt;

        i==&lt;span class="keyword"&gt;this&lt;/span&gt;.index ? 
          Element.addClassName&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.getEntry&lt;span class="bracket"&gt;(&lt;/span&gt;i&lt;span class="bracket"&gt;)&lt;/span&gt;,&lt;span class="string"&gt;"selected"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; : 
          Element.removeClassName&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.getEntry&lt;span class="bracket"&gt;(&lt;/span&gt;i&lt;span class="bracket"&gt;)&lt;/span&gt;,&lt;span class="string"&gt;"selected"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;

      &lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.hasFocus&lt;span class="bracket"&gt;)&lt;/span&gt; { 
        &lt;span class="keyword"&gt;this&lt;/span&gt;.show&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
        &lt;span class="keyword"&gt;this&lt;/span&gt;.active = &lt;span class="keyword"&gt;true&lt;/span&gt;;
        &lt;span class="comment"&gt;//scrolling for key events only

&lt;/span&gt;		&lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;scrollActive==&lt;span class="keyword"&gt;null&lt;/span&gt; || scrollActive==&lt;span class="keyword"&gt;true&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; {
			&lt;span class="keyword"&gt;var&lt;/span&gt; scrollAmt = &lt;span class="keyword"&gt;this&lt;/span&gt;.index &amp;gt; 0 ? &lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.index * 24&lt;span class="bracket"&gt;)&lt;/span&gt; - 2 : 0;
			&lt;span class="keyword"&gt;this&lt;/span&gt;.update.scrollTop=scrollAmt;
		}
	  } &lt;span class="keyword"&gt;else&lt;/span&gt; {
	      &lt;span class="keyword"&gt;this&lt;/span&gt;.active = &lt;span class="keyword"&gt;false&lt;/span&gt;;
	      &lt;span class="keyword"&gt;this&lt;/span&gt;.hide&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
	  }
	}
  },&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

Due to the aforementioned difficulty in getting the offsetHeight of a single LI in our list there is a bit of a kludge at line #14.  The index of the currently selected item times twenty four minus two seemed to work well in the browsers I tested with fairly large numbers of tags.  Post a comment if you know the right way to do it.  Also at line #2 there is a reference to a field we haven't seen yet (this.options.picked) we need to use this field to keep up with keyboard events...which we will need to override as well.  Speaking of events we need to make sure and take care of mouse events by overriding onHover:
&lt;div class="typocode"&gt;&lt;div class="codetitle"&gt;Inside AdminAutotag.prototype&lt;/div&gt;&lt;table class="typocode_linenumber"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="lineno"&gt;
1&lt;br /&gt;
2&lt;br /&gt;
3&lt;br /&gt;
4&lt;br /&gt;
5&lt;br /&gt;
6&lt;br /&gt;
7&lt;br /&gt;
8&lt;br /&gt;
9&lt;br /&gt;
10&lt;br /&gt;
11&lt;br /&gt;
12&lt;br /&gt;
13&lt;br /&gt;
14&lt;br /&gt;
15&lt;br /&gt;
16&lt;br /&gt;
17&lt;br /&gt;
18&lt;br /&gt;
19&lt;br /&gt;
20&lt;br /&gt;
21&lt;br /&gt;
22&lt;br /&gt;
&lt;/td&gt;&lt;td width="97%"&gt;&lt;pre class="javascript2"&gt;&lt;code&gt;  render: &lt;span class="keyword"&gt;function&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;scrollActive&lt;span class="bracket"&gt;)&lt;/span&gt; {
	&lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.index==0 &amp;amp;&amp;amp; &lt;span class="keyword"&gt;this&lt;/span&gt;.options.picked!=0&lt;span class="bracket"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;this&lt;/span&gt;.index = &lt;span class="keyword"&gt;this&lt;/span&gt;.&lt;span class="global"&gt;element&lt;/span&gt;.autocompleteIndex;
    &lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.entryCount &amp;gt; 0&lt;span class="bracket"&gt;)&lt;/span&gt; {
      &lt;span class="keyword"&gt;for&lt;/span&gt; &lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;var&lt;/span&gt; i = 0; i &amp;lt; &lt;span class="keyword"&gt;this&lt;/span&gt;.entryCount; i++&lt;span class="bracket"&gt;)&lt;/span&gt;

        i==&lt;span class="keyword"&gt;this&lt;/span&gt;.index ? 
          Element.addClassName&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.getEntry&lt;span class="bracket"&gt;(&lt;/span&gt;i&lt;span class="bracket"&gt;)&lt;/span&gt;,&lt;span class="string"&gt;"selected"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; : 
          Element.removeClassName&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.getEntry&lt;span class="bracket"&gt;(&lt;/span&gt;i&lt;span class="bracket"&gt;)&lt;/span&gt;,&lt;span class="string"&gt;"selected"&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;

      &lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.hasFocus&lt;span class="bracket"&gt;)&lt;/span&gt; { 
        &lt;span class="keyword"&gt;this&lt;/span&gt;.show&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
        &lt;span class="keyword"&gt;this&lt;/span&gt;.active = &lt;span class="keyword"&gt;true&lt;/span&gt;;
        &lt;span class="comment"&gt;//scrolling for key events only


&lt;/span&gt;		&lt;span class="keyword"&gt;if&lt;/span&gt;&lt;span class="bracket"&gt;(&lt;/span&gt;scrollActive==&lt;span class="keyword"&gt;null&lt;/span&gt; || scrollActive==&lt;span class="keyword"&gt;true&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt; {
			&lt;span class="keyword"&gt;var&lt;/span&gt; scrollAmt = &lt;span class="keyword"&gt;this&lt;/span&gt;.index &amp;gt; 0 ? &lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="keyword"&gt;this&lt;/span&gt;.index * 24&lt;span class="bracket"&gt;)&lt;/span&gt; - 2 : 0;
			&lt;span class="keyword"&gt;this&lt;/span&gt;.update.scrollTop=scrollAmt;
		}
	  } &lt;span class="keyword"&gt;else&lt;/span&gt; {
	      &lt;span class="keyword"&gt;this&lt;/span&gt;.active = &lt;span class="keyword"&gt;false&lt;/span&gt;;
	      &lt;span class="keyword"&gt;this&lt;/span&gt;.hide&lt;span class="bracket"&gt;(&lt;/span&gt;&lt;span class="bracket"&gt;)&lt;/span&gt;;
	  }
	}
  },&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

These are almost the same as their counterparts in Base, but the mark functions now update our this.options.picked, and our onHover passes false to the render method so as not to have you chasing the scrolling list of tags when you use the mouse.  Back to our _form.rhtml for the keywords field, it can now be simplified to just this:
&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_default "&gt;&lt;notextile&gt;&amp;lt;p&amp;gt;
  &amp;lt;label for=&amp;quot;article_keywords&amp;quot;&amp;gt;Keywords:&amp;lt;/label&amp;gt;&amp;lt;br/&amp;gt;
  &amp;lt;%= text_field 'article', 'keywords'  %&amp;gt;
&amp;lt;div id=&amp;quot;auto_complete&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; language=&amp;quot;javascript&amp;quot; charset=&amp;quot;utf-8&amp;quot;&amp;gt;
  opts = new Array(&amp;lt;%= @tags.sort_by{|t| t.display_name}.collect!{|t1| '&amp;quot;' + t1.display_name + '&amp;quot;,'} -%&amp;gt;'');
  new AdminAutotag('article_keywords','auto_complete', opts , { tokens: new Array(' ',',','\n')});
&amp;lt;/script&amp;gt;
&amp;lt;/p&amp;gt;&lt;/notextile&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
Since we use this functionality in admin, I put the CSS for the autocomplete there:
&lt;div class="css2"&gt;&lt;pre&gt;&lt;code&gt; &lt;span class="comment"&gt;/* public/stylesheets/administration.css */&lt;/span&gt;

&lt;span class="selectors"&gt;#auto_complete &lt;/span&gt;{
  &lt;span class="properties"&gt;width&lt;/span&gt;: 355&lt;span class="units"&gt;px&lt;/span&gt;;
  &lt;span class="properties"&gt;height&lt;/span&gt;: 150&lt;span class="units"&gt;px&lt;/span&gt;;
  &lt;span class="properties"&gt;background&lt;/span&gt;: #ffffff;
  &lt;span class="properties"&gt;overflow&lt;/span&gt;: auto;
}

&lt;span class="selectors"&gt;#auto_complete ul &lt;/span&gt;{
  &lt;span class="properties"&gt;border&lt;/span&gt;: 1&lt;span class="units"&gt;px&lt;/span&gt; solid #888888;
  &lt;span class="properties"&gt;margin&lt;/span&gt;:0;
  &lt;span class="properties"&gt;padding&lt;/span&gt;:0;
  &lt;span class="properties"&gt;width&lt;/span&gt;: 98%;
  &lt;span class="properties"&gt;list-style-type&lt;/span&gt;:none;
}

&lt;span class="selectors"&gt;#auto_complete ul li &lt;/span&gt;{
  &lt;span class="properties"&gt;margin&lt;/span&gt;:0;
  &lt;span class="properties"&gt;padding&lt;/span&gt;:0;
}

&lt;span class="selectors"&gt;#auto_complete ul li.selected &lt;/span&gt;{
  &lt;span class="properties"&gt;background-color&lt;/span&gt;: #d7dfe4;
}
&lt;span class="selectors"&gt;#auto_complete ul strong.highlight &lt;/span&gt;{
  &lt;span class="properties"&gt;color&lt;/span&gt;: #880000;
  &lt;span class="properties"&gt;margin&lt;/span&gt;:0;
  &lt;span class="properties"&gt;padding&lt;/span&gt;:0;
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;That is all there is too it.  Seems simple now huh?  The above code has been packed up as a patch against &lt;a href="http://typosphere.org"&gt;Typo&lt;/a&gt; and is detailed in &lt;a href="http://www.typosphere.org/trac/ticket/727"&gt;ticket #727&lt;/a&gt;.</description>
      <pubDate>Sun, 19 Mar 2006 20:43:00 +0000</pubDate>
      <guid isPermaLink="false">urn:uuid:ee411752-2d19-40d8-9a88-18cd77531acb</guid>
      <author>Steve Longdo</author>
      <link>http://www.stevelongdo.com/articles/2006/03/19/typo-administration-article-tagging-v</link>
      <category>scriptaculous</category>
      <category>css</category>
      <category>javascript</category>
      <category>rails</category>
      <category>ruby</category>
      <category>tag</category>
      <category>typo</category>
    </item>
  </channel>
</rss>
