title: Emulate SO tag editor
body: <p>I'm trying to implement a 'Tag Editor' field in my app, the same way as SO does.
  Right now i got this:</p>
  
  <p>EDIT: I have coded this into a jQuery plugin at:
  <a href="https://github.com/fernandotenorio/tagme.git" rel="nofollow">https://github.com/fernandotenorio/tagme.git</a></p>
  
  <h3>Fiddle</h3>
  
  <p><a href="http://jsfiddle.net/FernandoTen/PnYuF/" rel="nofollow">http://jsfiddle.net/FernandoTen/PnYuF/</a></p>
  
  <h3>html</h3>
  
  <pre><code>&lt;div style="margin-left: auto; margin-right: auto; width: 400px;"&gt;      
              &lt;span style='color: #333; font-size: small'&gt;
                  Tags [a-z A-Z 0-9 # + . -]
              &lt;/span&gt;
  
              &lt;div id='tags_container'&gt;   
                  &lt;span id='tags_queue'&gt;&lt;/span&gt;   
                  &lt;input type='text' id='tf' /&gt;
              &lt;/div&gt;
  &lt;/div&gt;
  </code></pre>
  
  <h3>js</h3>
  
  <pre><code>$(document).ready(function () {
  
  var rexp = /[^a-zA-Z0-9#+\.\-]/
  var left = 37;
  var right = 39;
  var del = 8;
  var space = 32;
  var comma = 188;
  var minlen = 3;
  var maxlen = 15;
  var current_tag = null
  
  $('#tf').focus().attr('maxlength', maxlen)
  
  $('#tags_container').click(function () {
      $('#tf').focus()
  })
  
  $('#tf').keydown(function (e) {
  
      var code = e.keyCode ? e.keyCode : e.which ? e.which : e.charCode;
      var txt = $(this).val().trim()
  
      // handle navigation between tags
      if ((code === left || code === right) &amp;&amp; txt.length === 0) {
  
          if (!current_tag) 
          {
              if (code === left) 
                  current_tag = $('#tags_queue span').last()
              else 
                  current_tag = $('#tags_queue span').first()
  
              if (current_tag.length &gt; 0) current_tag.css('background-color', '#9FC2D6')
          } else //current tag exists
          {
              if (code === left) 
                  current_tag = current_tag.css('background-color', '#B8D0DE').prev()
              else 
                  current_tag = current_tag.css('background-color', '#B8D0DE').next()
  
              if (current_tag.length &gt; 0) 
                  current_tag.css('background-color', '#9FC2D6')
              // hit last/first, clean current_tag
              else
              {                    
                  current_tag.css('background-color', '#B8D0DE')
                  current_tag = null
              }
          }            
      }
  });
  
  $('#tf').keypress(function (e) {
      var token = String.fromCharCode(e.which)
      var code = e.keyCode ? e.keyCode : e.which ? e.which : e.charCode;
  
      if (token.match(rexp) &amp;&amp; (code !== del) &amp;&amp; (code !== left) &amp;&amp; (code !== right)) 
          return false;                               
  });
  
  
  $('#tf').keyup(function (e) {
      var code = e.keyCode ? e.keyCode : e.which ? e.which : e.charCode;
      var txt = $(this).val().trim()
  
      if (code === del &amp;&amp; txt.length === 0 &amp;&amp; current_tag) {
          current_tag.remove()
          current_tag = null
          return
      }
  
      // clean current_tag, user is typing
      if (current_tag &amp;&amp; (code !== left) &amp;&amp; (code != right))
      {
          current_tag.css('background-color', '#B8D0DE')
          current_tag = null          
      }
  
      if (txt.length &gt; maxlen) txt = txt.substring(0, maxlen)
  
      if (txt.match(rexp)) {-
          $(this).val("")
          return
      } else if (code === space || code === comma) {
  
          if (txt.length &lt; minlen) return;
  
          var tag = $('&lt;span&gt;&lt;/span&gt;', {
              'class': 'tag'
          }).html(txt).append($('&lt;a&gt;&lt;/a&gt;', {
              'class': 'close_tag',
              html: '&amp;times',
              click: function () {                    
                  $(this).parent().fadeOut(function(){
                      $(this).remove()
                      current_tag.css('background-color', '#B8D0DE')
                      current_tag = null
                  })                    
              }
          }))
  
          $('#tags_queue').append(tag)
          $(this).val("")
      }
  });
  </code></pre>
  
  <p>})</p>
  
  <h3>css</h3>
  
  <pre><code>div#tags_container {
          border: 1px solid #ccc;
          padding-bottom: 5px;
          border-radius: 5px;
          background-color: beige;
          overflow: hidden;                                           
      }
      input#tf {
          border: 1px solid orange !important;
          outline: none !important;
          background-color: transparent !important;
          height: 30px;   
          margin-top: 2px;
          padding-left: 5px;
  
      }
      a.close_tag {
          margin-left: 5px;
          color: #fff;
      }
      a.close_tag:hover {
          cursor: pointer;
      }
      span.tag {
          background-color: #B8D0DE;
          color: #111;
          margin-left: 5px;
          margin-top: 5px;
          padding: 5px;
          float: left;
          border-radius: 5px;
      }
      span.tag:hover {
          background-color: #9FC2D6;
      }
      span#tags_queue {           
          margin-right: 5px;
          vertical-align: middle;
      }
  </code></pre>
  
  <p>The problem is, how can i handle the 'hidden' content using the arrows keys, the way SO does? It looks like i have to handle the cursor position inside the text-fields, and watch for arrow-left and arrow-right events...and there's also the click on the completed tags.</p>
  
  <p>I can think of the following (anti-SO design) solutions:</p>
  
  <ol>
  <li><p>remove <em><code>white-space: nowrap</code></em> from the <em><code>tags_container</code></em> div, and let
  the div 'grow' as the user add more tags.</p></li>
  <li><p>Add <em><code>overflow: scroll</code></em> to the <em><code>tags_container</code></em> div (very ugly!)</p></li>
  </ol>
  
  <p>So, i appreciate any new ideas on how to <strong>approximate</strong> the SO tag-editor behaviour.
  Thanks!</p>