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.