Author: weaverryan
Date: 2010-02-03 05:54:36 +0100 (Wed, 03 Feb 2010)
New Revision: 27465

Added:
   plugins/sfSympalTagsPlugin/branches/1.4/config/app.yml
   plugins/sfSympalTagsPlugin/branches/1.4/lib/validator/
   
plugins/sfSympalTagsPlugin/branches/1.4/lib/validator/sfValidatorTagString.class.php
   plugins/sfSympalTagsPlugin/branches/1.4/lib/widget/
   
plugins/sfSympalTagsPlugin/branches/1.4/lib/widget/sfWidgetFormTagString.class.php
   plugins/sfSympalTagsPlugin/branches/1.4/modules/
   plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/
   plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/actions/
   
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/actions/actions.class.php
   plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/lib/
   
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/lib/Basesympal_tagsActions.class.php
   plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/templates/
   
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/templates/autocompleteSuccess.php
   plugins/sfSympalTagsPlugin/branches/1.4/web/
   plugins/sfSympalTagsPlugin/branches/1.4/web/css/
   plugins/sfSympalTagsPlugin/branches/1.4/web/css/jquery.tokeninput.css
   plugins/sfSympalTagsPlugin/branches/1.4/web/js/
   plugins/sfSympalTagsPlugin/branches/1.4/web/js/jquery.tokeninput.js
Removed:
   plugins/sfSympalTagsPlugin/branches/1.4/lib/sfValidatorTagString.class.php
   plugins/sfSympalTagsPlugin/branches/1.4/lib/sfWidgetFormTagString.class.php
Modified:
   plugins/sfSympalTagsPlugin/branches/1.4/config/routing.yml
   
plugins/sfSympalTagsPlugin/branches/1.4/config/sfSympalTagsPluginConfiguration.class.php
   
plugins/sfSympalTagsPlugin/branches/1.4/lib/model/doctrine/PluginsfSympalTagTable.class.php
Log:
[1.4][sfSympalCommentsPlugin][1.0] Adding in jquery.token.input with 
autocomplete in the tags to assist in the presentation of the tags. It also 
helps to pick existing tags instead of just creating a billion, slightly 
different tags.


Added: plugins/sfSympalTagsPlugin/branches/1.4/config/app.yml
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/config/app.yml                      
        (rev 0)
+++ plugins/sfSympalTagsPlugin/branches/1.4/config/app.yml      2010-02-03 
04:54:36 UTC (rev 27465)
@@ -0,0 +1,6 @@
+all:
+  sympal_config:
+    tags:
+      jquery_token_input:
+        enabled:      true
+      max_suggestions: 10
\ No newline at end of file

Modified: plugins/sfSympalTagsPlugin/branches/1.4/config/routing.yml
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/config/routing.yml  2010-02-03 
02:23:51 UTC (rev 27464)
+++ plugins/sfSympalTagsPlugin/branches/1.4/config/routing.yml  2010-02-03 
04:54:36 UTC (rev 27465)
@@ -1 +1,4 @@
-# sfSympalTagsPlugin routing
\ No newline at end of file
+# sfSympalTagsPlugin routing
+sympal_tags_autocomplete:
+  url:    /tags/autocomplete
+  param:  { module: sympal_tags, action: autocomplete }
\ No newline at end of file

Modified: 
plugins/sfSympalTagsPlugin/branches/1.4/config/sfSympalTagsPluginConfiguration.class.php
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/config/sfSympalTagsPluginConfiguration.class.php
    2010-02-03 02:23:51 UTC (rev 27464)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/config/sfSympalTagsPluginConfiguration.class.php
    2010-02-03 04:54:36 UTC (rev 27465)
@@ -19,7 +19,20 @@
   {
     $this->dispatcher->connect('sympal_content.filter_generator_yaml', 
array($this, 'addTagsListToGeneratorYaml'));
     $this->dispatcher->connect('form.post_configure', array($this, 
'changeTagsListWidget'));
+    $this->dispatcher->connect('context.load_factories', array($this, 
'bootstrap'));
   }
+  
+  /**
+   * Listens to context.load_factories and performs any initial actions that
+   * this plugin requires
+   */
+  public function bootstrap(sfEvent $event)
+  {
+    sfConfig::set('sf_enabled_modules', array_merge(
+      sfConfig::get('sf_enabled_modules', array()),
+      array('sympal_tags')
+    ));
+  }
 
   public function addTagsListToGeneratorYaml(sfEvent $event, $generator)
   {

Modified: 
plugins/sfSympalTagsPlugin/branches/1.4/lib/model/doctrine/PluginsfSympalTagTable.class.php
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/lib/model/doctrine/PluginsfSympalTagTable.class.php
 2010-02-03 02:23:51 UTC (rev 27464)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/lib/model/doctrine/PluginsfSympalTagTable.class.php
 2010-02-03 04:54:36 UTC (rev 27465)
@@ -51,4 +51,35 @@
     $tagsList = array_unique($tagsList);
     return $tagsList;
   }
+  
+  /**
+   * The reverse of PluginsfSympalTagTable::getTagIdsFromString(), this
+   * turns an array of sfSympalTag ids into a csv string of tags
+   * 
+   * @param array $ids The array of sfSympalTag ids
+   */
+  public function getStringFromTagIds($ids)
+  {
+    if (!is_array($ids))
+    {
+      $ids = array();
+    }
+    
+    if ($ids)
+    {
+      $tags = $this->createQuery('t')
+        ->whereIn('t.id', $ids)
+        ->fetchArray();
+    } else {
+      $tags = array();
+    }
+
+    $tagsArray = array();
+    foreach ($tags as $tag)
+    {
+      $tagsArray[] = $tag['name'];
+    }
+    
+    return implode(', ', $tagsArray);
+  }
 }
\ No newline at end of file

Deleted: 
plugins/sfSympalTagsPlugin/branches/1.4/lib/sfValidatorTagString.class.php
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/lib/sfValidatorTagString.class.php  
2010-02-03 02:23:51 UTC (rev 27464)
+++ plugins/sfSympalTagsPlugin/branches/1.4/lib/sfValidatorTagString.class.php  
2010-02-03 04:54:36 UTC (rev 27465)
@@ -1,8 +0,0 @@
-<?php
-class sfValidatorTagString extends sfValidatorBase
-{
-  protected function doClean($value)
-  {
-    return Doctrine::getTable('sfSympalTag')->getTagIdsFromString($value);
-  }
-}
\ No newline at end of file

Deleted: 
plugins/sfSympalTagsPlugin/branches/1.4/lib/sfWidgetFormTagString.class.php
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/lib/sfWidgetFormTagString.class.php 
2010-02-03 02:23:51 UTC (rev 27464)
+++ plugins/sfSympalTagsPlugin/branches/1.4/lib/sfWidgetFormTagString.class.php 
2010-02-03 04:54:36 UTC (rev 27465)
@@ -1,27 +0,0 @@
-<?php
-class sfWidgetFormTagString extends sfWidgetFormTextarea
-{
-  public function render($name, $value = null, $attributes = array(), $errors 
= array())
-  {
-    if ($value)
-    {
-      $tags = Doctrine_Query::create()
-        ->from('sfSympalTag t')
-        ->whereIn('t.id', $value)
-        ->fetchArray();
-    } else {
-      $tags = array();
-    }
-
-    $tagsArray = array();
-    foreach ($tags as $tag)
-    {
-      $tagsArray[] = $tag['name'];
-    }
-    $value = implode(', ', $tagsArray);
-    $attributes['cols'] = 40;
-    $attributes['rows'] = 2;
-    $html  = $this->renderContentTag('textarea', self::escapeOnce($value), 
array_merge(array('name' => $name), $attributes));
-    return $html;
-  }
-}
\ No newline at end of file

Copied: 
plugins/sfSympalTagsPlugin/branches/1.4/lib/validator/sfValidatorTagString.class.php
 (from rev 27364, 
plugins/sfSympalTagsPlugin/branches/1.4/lib/sfValidatorTagString.class.php)
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/lib/validator/sfValidatorTagString.class.php
                                (rev 0)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/lib/validator/sfValidatorTagString.class.php
        2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,8 @@
+<?php
+class sfValidatorTagString extends sfValidatorBase
+{
+  protected function doClean($value)
+  {
+    return Doctrine::getTable('sfSympalTag')->getTagIdsFromString($value);
+  }
+}
\ No newline at end of file

Copied: 
plugins/sfSympalTagsPlugin/branches/1.4/lib/widget/sfWidgetFormTagString.class.php
 (from rev 27364, 
plugins/sfSympalTagsPlugin/branches/1.4/lib/sfWidgetFormTagString.class.php)
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/lib/widget/sfWidgetFormTagString.class.php
                          (rev 0)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/lib/widget/sfWidgetFormTagString.class.php
  2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * Special widget class for an input/textarea field where tags are specified
+ * 
+ * @package     sfSympalTagsPlugin
+ * @subpackage  widget
+ * @author      Jonathan H. Wage <[email protected]>
+ * @author      Ryan Weaver <[email protected]>
+ * @since       2010-01-31
+ * @version     svn:$Id$ $Author$
+ */
+class sfWidgetFormTagString extends sfWidgetForm
+{
+  /**
+   * Converts the incoming sfSympalTag ids into strings and renders them
+   */
+  public function render($name, $value = null, $attributes = array(), $errors 
= array())
+  {
+    $value = 
Doctrine_Core::getTable('sfSympalTag')->getStringFromTagIds($value);
+    
+    
+    if ($this->isjQueryTokenInputEnabled())
+    {
+      $widget = new sfWidgetFormInputText();
+      $html = $widget->render($name, self::escapeOnce($value), $attributes, 
$errors);
+      
+      $tokenOptions = $this->getTokenOptions();
+      
+      $id = $this->generateId($name, $value);
+      $html .= sprintf('
+        <script type="text/javascript">
+          $(document).ready(function()
+            {
+              $("#%s").tokenInput("%s", %s);
+            }
+          );
+        </script>
+        ',
+        $id,
+        url_for('@sympal_tags_autocomplete'),
+        $tokenOptions
+      );
+    }
+    else
+    {
+      $attributes['cols'] = 40;
+      $attributes['rows'] = 2;
+      $widget = new sfWidgetFormTextarea(array(), $attributes);
+      $html = $widget->render($name, self::escapeOnce($value), $attributes, 
$errors);
+    }
+    
+    return $html;
+  }
+  
+  /**
+   * Returns whether or not the jquery token input javascript should
+   * be used on this comment textbox
+   * 
+   * @return boolean
+   */
+  protected function isjQueryTokenInputEnabled()
+  {
+    $config = sfSympalConfig::get('tags', 'jquery_token_input');
+    
+    return (bool) $config['enabled'];
+  }
+  
+  /**
+   * Returns an array of javascripts needed for this widget
+   */
+  public function getJavascripts()
+  {
+    if ($this->isjQueryTokenInputEnabled())
+    {
+      return array('/sfSympalTagsPlugin/js/jquery.tokeninput.js');
+    }
+  }
+  
+  /**
+   * Returns an array of javascripts needed for this widget
+   */
+  public function getStylesheets()
+  {
+    if ($this->isjQueryTokenInputEnabled())
+    {
+      return array('/sfSympalTagsPlugin/css/jquery.tokeninput.css' => 
'screen');
+    }
+  }
+  
+  protected function getTokenOptions()
+  {
+    $options = '
+{
+  classes: {
+      tokenList: "token-input-list",
+      token: "token-input-token",
+      tokenDelete: "token-input-delete-token",
+      selectedToken: "token-input-selected-token",
+      highlightedToken: "token-input-highlighted-token",
+      dropdown: "token-input-dropdown",
+      dropdownItem: "token-input-dropdown-item",
+      dropdownItem2: "token-input-dropdown-item2",
+      selectedDropdownItem: "token-input-selected-dropdown-item",
+      inputToken: "token-input-input-token",
+  },
+  minChars:   1,
+  allowNewValues: true,
+  prePopulateFromInput: true,
+  queryParam: \'current\',
+}';
+    
+    return $options;
+  }
+}
\ No newline at end of file

Added: 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/actions/actions.class.php
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/actions/actions.class.php
                               (rev 0)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/actions/actions.class.php
       2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,7 @@
+<?php
+
+require_once dirname(__FILE__).'/../lib/Basesympal_tagsActions.class.php';
+
+class sympal_tagsActions extends Basesympal_tagsActions
+{
+}
\ No newline at end of file

Added: 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/lib/Basesympal_tagsActions.class.php
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/lib/Basesympal_tagsActions.class.php
                            (rev 0)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/lib/Basesympal_tagsActions.class.php
    2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Actions class for sympal tags - contains the autocomplete action
+ * 
+ * @package     sfSympalTagsPlugin
+ * @subpackage  actions
+ * @author      Ryan Weaver <[email protected]>
+ * @since       2010-01-31
+ * @version     svn:$Id$ $Author$
+ */
+class Basesympal_tagsActions extends sfActions
+{
+  /**
+   * Processor action for the autocomplete. This attempts to pick out
+   * exactly what you've typed in so far and suggest what you might
+   * be getting at
+   * 
+   * The large majority of this logic comes from sfDoctrineActAsTaggablePlugin:
+   *   Tom Boutell, P'unk Avenue, www.punkave.com
+   */
+  public function executeAutocomplete(sfWebRequest $request)
+  {
+    $this->setLayout(false);
+    
+    $tags = $this->getRequestParameter('current');
+    $tags = explode(',', $tags);
+    $currentTag = trim(array_pop($tags));
+    
+    $this->tagSuggestions = array();
+    if ($currentTag)
+    {
+      $suggestedTags = Doctrine_Query::create()
+        ->from('sfSympalTag t')
+        ->select('t.name')
+        ->where('t.name LIKE ?', $currentTag . '%')
+        ->limit(sfSympalConfig::get('tags', 'max_suggestions', 10))
+        ->execute(null, Doctrine::HYDRATE_NONE);
+      
+      foreach ($suggestedTags as $suggestedTag)
+      {
+        if ($suggestedTag != $currentTag)
+        {
+          $this->tagSuggestions[] = $suggestedTag[0];
+        }
+      }
+    }
+  }
+}
\ No newline at end of file

Added: 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/templates/autocompleteSuccess.php
===================================================================
--- 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/templates/autocompleteSuccess.php
                               (rev 0)
+++ 
plugins/sfSympalTagsPlugin/branches/1.4/modules/sympal_tags/templates/autocompleteSuccess.php
       2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,5 @@
+[
+       <?php foreach($tagSuggestions as $suggestion) : ?>
+    {"id":"<?php echo $suggestion ?>", "name":"<?php echo $suggestion ?>"},
+  <?php endforeach; ?>
+]

Added: plugins/sfSympalTagsPlugin/branches/1.4/web/css/jquery.tokeninput.css
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/web/css/jquery.tokeninput.css       
                        (rev 0)
+++ plugins/sfSympalTagsPlugin/branches/1.4/web/css/jquery.tokeninput.css       
2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,111 @@
+/* Example tokeninput style #2: Facebook style */
+ul.token-input-list {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 1%;
+    cursor: text;
+    min-height: 1px;
+    z-index: 999;
+    margin: 0;
+    padding: 0;
+    background-color: #fff;
+}
+
+ul.token-input-list {
+    list-style-type: none;
+}
+
+ul.token-input-list li input {
+    border: 0;
+    width: 200px !important;
+    padding: 3px 8px;
+    background-color: white;
+    margin: 2px 0;
+}
+
+li.token-input-token {
+    overflow: hidden; 
+    height: auto !important; 
+    height: 1%;
+    margin: 3px;
+    padding: 1px 3px;
+    background-color: #eff2f7;
+    color: #000;
+    cursor: default;
+    border: 1px solid #ccd5e4;
+    font-size: 11px;
+    float: left;
+}
+
+li.token-input-token p {
+    display: inline;
+    padding: 0;
+    margin: 0;
+}
+
+li.token-input-token span {
+    color: #a6b3cf;
+    margin-left: 5px;
+    font-weight: bold;
+    cursor: pointer;
+}
+
+li.token-input-selected-token {
+    background-color: #5670a6;
+    border: 1px solid #3b5998;
+    color: #fff;
+}
+
+li.token-input-input-token {
+    float: left;
+}
+
+div.token-input-dropdown {
+    position: absolute;
+    width: 400px;
+    background-color: #fff;
+    overflow: hidden;
+    border-left: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    border-bottom: 1px solid #ccc;
+    cursor: default;
+    font-size: 11px;
+    font-family: Verdana;
+    z-index: 999;
+}
+
+div.token-input-dropdown p {
+    margin: 0;
+    padding: 5px;
+    font-weight: bold;
+    color: #777;
+}
+
+div.token-input-dropdown ul {
+    margin: 0;
+    padding: 0;
+}
+
+div.token-input-dropdown ul li {
+    background-color: #fff;
+    padding: 3px;
+}
+
+div.token-input-dropdown ul li.token-input-dropdown-item {
+    background-color: #fff;
+}
+
+div.token-input-dropdown ul li.token-input-dropdown-item2 {
+    background-color: #fff;
+}
+
+div.token-input-dropdown ul li em {
+    font-weight: bold;
+    font-style: none;
+}
+
+div.token-input-dropdown ul li.token-input-selected-dropdown-item {
+    background-color: #3b5998;
+    color: #fff;
+}
+

Added: plugins/sfSympalTagsPlugin/branches/1.4/web/js/jquery.tokeninput.js
===================================================================
--- plugins/sfSympalTagsPlugin/branches/1.4/web/js/jquery.tokeninput.js         
                (rev 0)
+++ plugins/sfSympalTagsPlugin/branches/1.4/web/js/jquery.tokeninput.js 
2010-02-03 04:54:36 UTC (rev 27465)
@@ -0,0 +1,632 @@
+/*
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
+ * Version 1.1
+ *
+ * Copyright (c) 2009 James Smith (http://loopj.com)
+ * Licensed jointly under the GPL and MIT licenses,
+ * choose which one suits your project best!
+ * 
+ * Brought in new version of this plugin from:
+ * 
http://loopj.com/2009/04/25/jquery-plugin-tokenizing-autocomplete-text-entry/#comment-13232689
+ *
+ */
+
+(function($) {
+
+$.fn.tokenInput = function (url, options) {
+    var settings = $.extend({
+        url: url,
+        hintText: "Type in a search term",
+        noResultsText: "No results",
+        searchingText: "Searching...",
+        searchDelay: 300,
+        allowNewValues: false,
+        prePopulateFromInput: false,
+        minChars: 1,
+        tokenLimit: null,
+        jsonContainer: null,
+        method: "GET",
+        contentType: "json",
+        queryParam: "q",
+        onResult: null
+    }, options);
+
+    settings.classes = $.extend({
+        tokenList: "token-input-list",
+        token: "token-input-token",
+        tokenDelete: "token-input-delete-token",
+        selectedToken: "token-input-selected-token",
+        highlightedToken: "token-input-highlighted-token",
+        dropdown: "token-input-dropdown",
+        dropdownItem: "token-input-dropdown-item",
+        dropdownItem2: "token-input-dropdown-item2",
+        selectedDropdownItem: "token-input-selected-dropdown-item",
+        inputToken: "token-input-input-token"
+    }, options.classes);
+    
+    return this.each(function () {
+        var list = new $.TokenList(this, settings);
+    });
+};
+
+$.TokenList = function (input, settings) {
+    //
+    // Variables
+    //
+
+    // Input box position "enum"
+    var POSITION = {
+        BEFORE: 0,
+        AFTER: 1,
+        END: 2
+    };
+
+    // Keys "enum"
+    var KEY = {
+        BACKSPACE: 8,
+        TAB: 9,
+        RETURN: 13,
+        ESC: 27,
+        LEFT: 37,
+        UP: 38,
+        RIGHT: 39,
+        DOWN: 40,
+        COMMA: 188
+    };
+
+    // Save the tokens
+    var saved_tokens = [];
+    
+    // Keep track of the number of tokens in the list
+    var token_count = 0;
+
+    // Basic cache to save on db hits
+    var cache = new $.TokenList.Cache();
+
+    // Keep track of the timeout
+    var timeout;
+
+    // Create a new text input an attach keyup events
+    var input_box = $("<input type=\"text\">")
+        .css({
+            outline: "none",
+        })
+        .focus(function () {
+            if (settings.tokenLimit == null || settings.tokenLimit != 
token_count) {
+                show_dropdown_hint();
+            }
+        })
+        .blur(function () {
+               // If the user has been typing, create what they typed as a new 
value
+               if(settings.allowNewValues) create_new_token();
+               
+            hide_dropdown();
+        })
+        .keydown(function (event) {
+            var previous_token;
+            var next_token;
+
+            switch(event.keyCode) {
+                case KEY.LEFT:
+                case KEY.RIGHT:
+                case KEY.UP:
+                case KEY.DOWN:
+                    if(!$(this).val()) {
+                        previous_token = input_token.prev();
+                        next_token = input_token.next();
+
+                        if((previous_token.length && previous_token.get(0) === 
selected_token) || (next_token.length && next_token.get(0) === selected_token)) 
{
+                            // Check if there is a previous/next token and it 
is selected
+                            if(event.keyCode == KEY.LEFT || event.keyCode == 
KEY.UP) {
+                                deselect_token($(selected_token), 
POSITION.BEFORE);
+                            } else {
+                                deselect_token($(selected_token), 
POSITION.AFTER);
+                            }
+                        } else if((event.keyCode == KEY.LEFT || event.keyCode 
== KEY.UP) && previous_token.length) {
+                            // We are moving left, select the previous token 
if it exists
+                            select_token($(previous_token.get(0)));
+                        } else if((event.keyCode == KEY.RIGHT || event.keyCode 
== KEY.DOWN) && next_token.length) {
+                            // We are moving right, select the next token if 
it exists
+                            select_token($(next_token.get(0)));
+                        }
+                    } else {
+                        var dropdown_item = null;
+
+                        if(event.keyCode == KEY.DOWN || event.keyCode == 
KEY.RIGHT) {
+                            dropdown_item = $(selected_dropdown_item).next();
+                        } else {
+                            dropdown_item = $(selected_dropdown_item).prev();
+                        }
+
+                        if(dropdown_item.length) {
+                            select_dropdown_item(dropdown_item);
+                        }
+                        return false;
+                    }
+                    break;
+
+                case KEY.BACKSPACE:
+                    previous_token = input_token.prev();
+
+                    if(!$(this).val().length) {
+                        if(selected_token) {
+                            delete_token($(selected_token));
+                        } else if(previous_token.length) {
+                            select_token($(previous_token.get(0)));
+                        }
+
+                        return false;
+                    } else if($(this).val().length == 1) {
+                        hide_dropdown();
+                    } else {
+                        // set a timeout just long enough to let this function 
finish.
+                        setTimeout(function(){do_search(false);}, 5);
+                    }
+                    break;
+
+                case KEY.TAB:
+                case KEY.RETURN:
+                case KEY.COMMA:
+                  if(selected_dropdown_item) {
+                    var li_data = $.data($(selected_dropdown_item).get(0), 
"tokeninput");
+                    add_token(li_data.id, li_data.name);
+                    return false;
+                  } else if(settings.allowNewValues) {
+                       create_new_token();
+                       return false;
+                  }
+                  break;
+
+                case KEY.ESC:
+                  hide_dropdown();
+                  return true;
+
+                default:
+                    if(is_printable_character(event.keyCode)) {
+                      // set a timeout just long enough to let this function 
finish.
+                      setTimeout(function(){do_search(false);}, 5);
+                    }
+                    break;
+            }
+        });
+
+    // Keep a reference to the original input box
+    var hidden_input = $(input)
+                           .hide()
+                           .focus(function () {
+                               input_box.focus();
+                           })
+                           .blur(function () {
+                               input_box.blur();
+                           });
+
+    // Keep a reference to the selected token and dropdown item
+    var selected_token = null;
+    var selected_dropdown_item = null;
+
+    // The list to store the token items in
+    var token_list = $("<ul />")
+       //.css('width', hidden_input.width())
+        .addClass(settings.classes.tokenList)
+        .insertAfter(hidden_input)
+        .click(function (event) {
+            var li = get_element_from_event(event, "li");
+            if(li && li.get(0) != input_token.get(0)) {
+                toggle_select_token(li);
+                return false;
+            } else {
+                input_box.focus();
+
+                if(selected_token) {
+                    deselect_token($(selected_token), POSITION.END);
+                }
+            }
+        })
+        .mouseover(function (event) {
+            var li = get_element_from_event(event, "li");
+            if(li && selected_token !== this) {
+                li.addClass(settings.classes.highlightedToken);
+            }
+        })
+        .mouseout(function (event) {
+            var li = get_element_from_event(event, "li");
+            if(li && selected_token !== this) {
+                li.removeClass(settings.classes.highlightedToken);
+            }
+        })
+        .mousedown(function (event) {
+            // Stop user selecting text on tokens
+            var li = get_element_from_event(event, "li");
+            if(li){
+                return false;
+            }
+        });
+
+
+    // The list to store the dropdown items in
+    var dropdown = $("<div>")
+        .addClass(settings.classes.dropdown)
+        .insertAfter(token_list)
+        .hide();
+
+    // The token holding the input box
+    var input_token = $("<li />")
+        .addClass(settings.classes.inputToken)
+        .appendTo(token_list)
+        .append(input_box);
+
+    init_list();
+
+    //
+    // Functions
+    //
+
+
+    // Pre-populate list if items exist
+    function init_list () {
+        li_data = settings.prePopulate;
+        if(li_data && li_data.length) {
+            for(var i in li_data) {
+                var this_token = $("<li><p>"+li_data[i].name+"</p> </li>")
+                    .addClass(settings.classes.token)
+                    .insertBefore(input_token);
+
+                $("<span>x</span>")
+                    .addClass(settings.classes.tokenDelete)
+                    .appendTo(this_token)
+                    .click(function () {
+                        delete_token($(this).parent());
+                        return false;
+                    });
+
+                $.data(this_token.get(0), "tokeninput", {"id": li_data[i].id, 
"name": li_data[i].name});
+
+                // Clear input box and make sure it keeps focus
+                input_box
+                    .val("")
+                    .focus();
+
+                // Don't show the help dropdown, they've got the idea
+                hide_dropdown();
+
+                // Save this token id
+                var id_string = li_data[i].id + ","
+                hidden_input.val(hidden_input.val() + id_string);
+            }
+        } else if(settings.prePopulateFromInput) {
+               var values = hidden_input.val().split(',');
+               hidden_input.val('');
+               
+               $.each(values, function() {
+                       var value = $.trim(this);
+                       if(value.length > 0) add_token(value, value);
+               });
+        } else {
+               hidden_input.val('');
+        }
+    }
+
+    function is_printable_character(keycode) {
+        if((keycode >= 48 && keycode <= 90) ||      // 0-1a-z
+           (keycode >= 96 && keycode <= 111) ||     // numpad 0-9 + - / * .
+           (keycode >= 186 && keycode <= 192) ||    // ; = , - . / ^
+           (keycode >= 219 && keycode <= 222)       // ( \ ) '
+          ) {
+              return true;
+          } else {
+              return false;
+          }
+    }
+
+    // Get an element of a particular type from an event (click/mouseover etc)
+    function get_element_from_event (event, element_type) {
+        var target = $(event.target);
+        var element = null;
+
+        if(target.is(element_type)) {
+            element = target;
+        } else if(target.parent(element_type).length) {
+            element = target.parent(element_type+":first");
+        }
+
+        return element;
+    }
+
+    // Inner function to a token to the list
+    function insert_token(id, value) {
+      var this_token = $("<li><p>"+ value +"</p> </li>")
+      .addClass(settings.classes.token)
+      .insertBefore(input_token);
+
+      // The 'delete token' button
+      $("<span>x</span>")
+          .addClass(settings.classes.tokenDelete)
+          .appendTo(this_token)
+          .click(function () {
+              delete_token($(this).parent());
+              return false;
+          });
+
+      $.data(this_token.get(0), "tokeninput", {"id": id, "name": value});
+
+      return this_token;
+    }
+
+    // Add a token to the token list based on user input
+    function add_token (id, name) {
+        var this_token = insert_token(id, name);
+
+        // Clear input box and make sure it keeps focus
+        input_box
+            .val("")
+            .focus();
+
+        // Don't show the help dropdown, they've got the idea
+        hide_dropdown();
+
+        // Save this token id
+        var id_string = id + ","
+        hidden_input.val(hidden_input.val() + id_string);
+        
+        token_count++;
+        
+        if(settings.tokenLimit != null && settings.tokenLimit >= token_count) {
+            input_box.hide();
+            hide_dropdown();
+        }
+    }
+    
+    function create_new_token () {
+       var string = input_box.val().toLowerCase();
+       if(string.length > 0) add_token(string, string);
+    }
+
+    // Select a token in the token list
+    function select_token (token) {
+        token.addClass(settings.classes.selectedToken);
+        selected_token = token.get(0);
+
+        // Hide input box
+        input_box.val("");
+
+        // Hide dropdown if it is visible (eg if we clicked to select token)
+        hide_dropdown();
+    }
+
+    // Deselect a token in the token list
+    function deselect_token (token, position) {
+        token.removeClass(settings.classes.selectedToken);
+        selected_token = null;
+
+        if(position == POSITION.BEFORE) {
+            input_token.insertBefore(token);
+        } else if(position == POSITION.AFTER) {
+            input_token.insertAfter(token);
+        } else {
+            input_token.appendTo(token_list);
+        }
+
+        // Show the input box and give it focus again
+        input_box.focus();
+    }
+
+    // Toggle selection of a token in the token list
+    function toggle_select_token (token) {
+        if(selected_token == token.get(0)) {
+            deselect_token(token, POSITION.END);
+        } else {
+            if(selected_token) {
+                deselect_token($(selected_token), POSITION.END);
+            }
+            select_token(token);
+        }
+    }
+
+    // Delete a token from the token list
+    function delete_token (token) {
+        // Remove the id from the saved list
+        var token_data = $.data(token.get(0), "tokeninput");
+
+        // Delete the token
+        token.remove();
+        selected_token = null;
+
+        // Show the input box and give it focus again
+        input_box.focus();
+
+        // Delete this token's id from hidden input
+        var str = hidden_input.val()
+        var start = str.indexOf(token_data.id+",");
+        var end = str.indexOf(",", start) + 1;
+
+        if(end >= str.length) {
+            hidden_input.val(str.slice(0, start));
+        } else {
+            hidden_input.val(str.slice(0, start) + str.slice(end, str.length));
+        }
+        
+        token_count--;
+        
+        if (settings.tokenLimit != null) {
+            input_box
+                .show()
+                .val("")
+                .focus();
+        }
+    }
+
+    // Hide and clear the results dropdown
+    function hide_dropdown () {
+        dropdown.hide().empty();
+        selected_dropdown_item = null;
+    }
+
+    function show_dropdown_searching () {
+       if(settings.searchingText.length > 0) {
+               dropdown
+                   .html("<p>"+settings.searchingText+"</p>")
+                   .show();
+       }
+    }
+
+    function show_dropdown_hint () {
+        dropdown
+            .html("<p>"+settings.hintText+"</p>")
+            .show();
+    }
+
+    // Highlight the query part of the search term
+       function highlight_term(value, term) {
+               return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + 
term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+       }
+
+    // Populate the results dropdown with some results
+    function populate_dropdown (query, results) {
+        if(results.length) {
+            dropdown.empty();
+            var dropdown_ul = $("<ul>")
+                .appendTo(dropdown)
+                .mouseover(function (event) {
+                    select_dropdown_item(get_element_from_event(event, "li"));
+                })
+                .click(function (event) {
+                    var item = get_element_from_event(event, "li");
+                    var the_data = $.data(item.get(0), "tokeninput");
+                    add_token(the_data.id, the_data.name);
+                })
+                .mousedown(function (event) {
+                    // Stop user selecting text on tokens
+                    return false;
+                })
+                .hide();
+
+            for(var i in results) {
+                if (results.hasOwnProperty(i)) {
+                    var this_li = $("<li>"+highlight_term(results[i].name, 
query)+"</li>")
+                                      .appendTo(dropdown_ul);
+
+                    if(i%2) {
+                        this_li.addClass(settings.classes.dropdownItem);
+                    } else {
+                        this_li.addClass(settings.classes.dropdownItem2);
+                    }
+
+                    if(i == 0) {
+                        select_dropdown_item(this_li);
+                    }
+
+                    $.data(this_li.get(0), "tokeninput", {"id": results[i].id, 
"name": results[i].name});
+                }
+            }
+
+            dropdown.show();
+            dropdown_ul.show();
+
+        } else {
+               if(settings.noResultsText.length > 0) {
+                   dropdown
+                       .html("<p>"+settings.noResultsText+"</p>")
+                       .show();
+               } else {
+                       hide_dropdown();
+               }
+        }
+    }
+
+    // Highlight an item in the results dropdown
+    function select_dropdown_item (item) {
+        if(item) {
+            if(selected_dropdown_item) {
+                deselect_dropdown_item($(selected_dropdown_item));
+            }
+
+            item.addClass(settings.classes.selectedDropdownItem);
+            selected_dropdown_item = item.get(0);
+        }
+    }
+
+    // Remove highlighting from an item in the results dropdown
+    function deselect_dropdown_item (item) {
+        item.removeClass(settings.classes.selectedDropdownItem);
+        selected_dropdown_item = null;
+    }
+
+    // Do a search and show the "searching" dropdown if the input is longer
+    // than settings.minChars
+    function do_search(immediate) {
+        var query = input_box.val().toLowerCase();
+
+        if (query && query.length) {
+            if(selected_token) {
+                deselect_token($(selected_token), POSITION.AFTER);
+            }
+            if (query.length >= settings.minChars) {
+                show_dropdown_searching();
+                if (immediate) {
+                    run_search(query);
+                } else {
+                    clearTimeout(timeout);
+                    timeout = setTimeout(function(){run_search(query);}, 
settings.searchDelay);
+                }
+            } else {
+                hide_dropdown();
+            }
+        }
+    }
+
+    // Do the actual search
+    function run_search(query) {
+        var cached_results = cache.get(query);
+        if(cached_results) {
+            populate_dropdown(query, cached_results);
+        } else {
+                       var queryStringDelimiter = settings.url.indexOf("?") < 
0 ? "?" : "&";
+                       var callback = function(results) {
+                         if($.isFunction(settings.onResult)) {
+                             results = settings.onResult.call(this, results);
+                         }
+              cache.add(query, settings.jsonContainer ? 
results[settings.jsonContainer] : results);
+              populate_dropdown(query, settings.jsonContainer ? 
results[settings.jsonContainer] : results);
+            };
+            
+            if(settings.method == "POST") {
+                           $.post(settings.url, settings.queryParam + "=" + 
query, callback, settings.contentType);
+                   } else {
+                       $.get(settings.url + queryStringDelimiter + 
settings.queryParam + "=" + query, {}, callback, settings.contentType);
+                   }
+        }
+    }
+};
+
+// Really basic cache for the results
+$.TokenList.Cache = function (options) {
+    var settings = $.extend({
+        max_size: 50
+    }, options);
+
+    var data = {};
+    var size = 0;
+
+    var flush = function () {
+        data = {};
+        size = 0;
+    };
+
+    this.add = function (query, results) {
+        if(size > settings.max_size) {
+            flush();
+        }
+
+        if(!data[query]) {
+            size++;
+        }
+
+        data[query] = results;
+    };
+
+    this.get = function (query) {
+        return data[query];
+    };
+};
+
+})(jQuery);

-- 
You received this message because you are subscribed to the Google Groups 
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/symfony-svn?hl=en.

Reply via email to