diff options
-rw-r--r-- | admin.php | 13 | ||||
-rw-r--r-- | admin/batch_manager.php | 12 | ||||
-rw-r--r-- | admin/batch_manager_global.php | 2 | ||||
-rw-r--r-- | admin/batch_manager_unit.php | 4 | ||||
-rw-r--r-- | admin/include/functions.php | 15 | ||||
-rw-r--r-- | admin/picture_modify.php | 13 | ||||
-rw-r--r-- | admin/themes/default/template/batch_manager_global.tpl | 32 | ||||
-rw-r--r-- | admin/themes/default/template/batch_manager_unit.tpl | 38 | ||||
-rw-r--r-- | admin/themes/default/template/picture_modify.tpl | 34 | ||||
-rw-r--r-- | admin/themes/default/theme.css | 62 | ||||
-rw-r--r-- | admin/themes/roma/theme.css | 7 | ||||
-rw-r--r-- | language/en_UK/admin.lang.php | 4 | ||||
-rw-r--r-- | language/fr_FR/admin.lang.php | 4 | ||||
-rw-r--r-- | themes/default/js/plugins/jquery.fcbkcomplete.js | 645 | ||||
-rw-r--r-- | themes/default/js/plugins/jquery.tokeninput.js | 777 |
15 files changed, 901 insertions, 761 deletions
@@ -44,19 +44,6 @@ check_status(ACCESS_ADMINISTRATOR); // | Direct actions | // +-----------------------------------------------------------------------+ -// tags -if (isset($_GET['fckb_tags'])) -{ - $query = ' -SELECT - id AS tag_id, - name AS tag_name - FROM '.TAGS_TABLE.' -;'; - echo json_encode(get_fckb_taglist($query)); - exit(); -} - // theme changer if (isset($_GET['change_theme'])) { diff --git a/admin/batch_manager.php b/admin/batch_manager.php index b333ca9ac..c1d3687d4 100644 --- a/admin/batch_manager.php +++ b/admin/batch_manager.php @@ -361,6 +361,18 @@ if (in_array($page['tab'], $tab_codes)) $tabsheet->assign(); // +-----------------------------------------------------------------------+ +// | tags | +// +-----------------------------------------------------------------------+ + +$query = ' +SELECT + id AS tag_id, + name AS tag_name + FROM '.TAGS_TABLE.' +;'; +$template->assign('tags', get_taglist($query)); + +// +-----------------------------------------------------------------------+ // | open specific mode | // +-----------------------------------------------------------------------+ diff --git a/admin/batch_manager_global.php b/admin/batch_manager_global.php index 11cafe02d..73d11be2d 100644 --- a/admin/batch_manager_global.php +++ b/admin/batch_manager_global.php @@ -123,7 +123,7 @@ DELETE } else { - $tag_ids = get_fckb_tag_ids($_POST['add_tags']); + $tag_ids = get_tag_ids($_POST['add_tags']); add_tags($tag_ids, $collection); if ('with no tag' == $page['prefilter']) diff --git a/admin/batch_manager_unit.php b/admin/batch_manager_unit.php index aa28b856e..b7ca074d7 100644 --- a/admin/batch_manager_unit.php +++ b/admin/batch_manager_unit.php @@ -108,7 +108,7 @@ SELECT id, date_creation // tags management if (isset($_POST[ 'tags-'.$row['id'] ])) { - $tag_ids = get_fckb_tag_ids($_POST[ 'tags-'.$row['id'] ]); + $tag_ids = get_tag_ids($_POST[ 'tags-'.$row['id'] ]); set_tags($tag_ids, $row['id']); } } @@ -256,7 +256,7 @@ SELECT JOIN '.TAGS_TABLE.' AS t ON t.id = it.tag_id WHERE image_id = '.$row['id'].' ;'; - $tag_selection = get_fckb_taglist($query); + $tag_selection = get_taglist($query); $template->append( 'elements', diff --git a/admin/include/functions.php b/admin/include/functions.php index f5afa9633..c929d2c55 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -2100,25 +2100,29 @@ function get_active_menu($menu_page) return 0; } -function get_fckb_taglist($query) +function get_taglist($query) { $result = pwg_query($query); + $taglist = array(); while ($row = pwg_db_fetch_assoc($result)) { array_push( $taglist, array( - 'key' => $row['tag_name'], - 'value' => '~~'.$row['tag_id'].'~~', + 'name' => $row['tag_name'], + 'id' => '~~'.$row['tag_id'].'~~', ) ); } - + + $cmp = create_function('$a,$b', 'return strcasecmp($a["name"], $b["name"]);'); + usort($taglist, $cmp); + return $taglist; } -function get_fckb_tag_ids($raw_tags) +function get_tag_ids($raw_tags) { // In $raw_tags we receive something like array('~~6~~', '~~59~~', 'New // tag', 'Another new tag') The ~~34~~ means that it is an existing @@ -2126,6 +2130,7 @@ function get_fckb_tag_ids($raw_tags) // or "1234" (numeric characters only) $tag_ids = array(); + $raw_tags = explode(',',$raw_tags); foreach ($raw_tags as $raw_tag) { diff --git a/admin/picture_modify.php b/admin/picture_modify.php index 333bf3e88..97bff6e5e 100644 --- a/admin/picture_modify.php +++ b/admin/picture_modify.php @@ -166,7 +166,7 @@ if (isset($_POST['submit']) and count($page['errors']) == 0) $tag_ids = array(); if (isset($_POST['tags'])) { - $tag_ids = get_fckb_tag_ids($_POST['tags']); + $tag_ids = get_tag_ids($_POST['tags']); } set_tags($tag_ids, $_GET['image_id']); @@ -233,7 +233,15 @@ SELECT JOIN '.TAGS_TABLE.' AS t ON t.id = it.tag_id WHERE image_id = '.$_GET['image_id'].' ;'; -$tags = get_fckb_taglist($query); +$tag_selection = get_taglist($query); + +$query = ' +SELECT + id AS tag_id, + name AS tag_name + FROM '.TAGS_TABLE.' +;'; +$tags = get_taglist($query); // retrieving direct information about picture $query = ' @@ -267,6 +275,7 @@ $admin_url_start.= isset($_GET['cat_id']) ? '&cat_id='.$_GET['cat_id'] : ''; $template->assign( array( + 'tag_selection' => $tag_selection, 'tags' => $tags, 'U_SYNC' => $admin_url_start.'&sync_metadata=1', 'U_DELETE' => $admin_url_start.'&delete=1&pwg_token='.get_pwg_token(), diff --git a/admin/themes/default/template/batch_manager_global.tpl b/admin/themes/default/template/batch_manager_global.tpl index cadd7ce13..b01a120a1 100644 --- a/admin/themes/default/template/batch_manager_global.tpl +++ b/admin/themes/default/template/batch_manager_global.tpl @@ -5,22 +5,24 @@ pwg_initialization_datepicker("#date_creation_day", "#date_creation_month", "#date_creation_year", "#date_creation_linked_date", "#date_creation_action_set"); {/literal}{/footer_script} -{combine_script id='jquery.fcbkcomplete' load='footer' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'} - -{footer_script require='jquery.fcbkcomplete'}{literal} -jQuery(document).ready(function() { - jQuery("#tags").fcbkcomplete({ - json_url: "admin.php?fckb_tags=1", - cache: false, - filter_case: false, - filter_hide: true, - firstselected: true, - filter_selected: true, - maxitems: 100, - newel: true - }); +{combine_script id='jquery.tokeninput' load='footer' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'} + +{footer_script require='jquery.tokeninput'} +jQuery(document).ready(function() {ldelim} + jQuery("#tags").tokenInput( + [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}], + {ldelim} + hintText: '{'Type in a search term'|@translate}', + noResultsText: '{'No results'|@translate}', + searchingText: '{'Searching...'|@translate}', + newText: ' ({'new'|@translate})', + animateDropdown: false, + preventDuplicates: true, + allowCreation: true + } + ); }); -{/literal}{/footer_script} +{/footer_script} {footer_script} var nb_thumbs_page = {$nb_thumbs_page}; diff --git a/admin/themes/default/template/batch_manager_unit.tpl b/admin/themes/default/template/batch_manager_unit.tpl index 9a1dde834..66b8b6b17 100644 --- a/admin/themes/default/template/batch_manager_unit.tpl +++ b/admin/themes/default/template/batch_manager_unit.tpl @@ -2,8 +2,8 @@ {include file='include/datepicker.inc.tpl'} {include file='include/colorbox.inc.tpl'} -{combine_script id='jquery.fcbkcomplete' load='async' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'} -{footer_script require='jquery.fcbkcomplete'} +{combine_script id='jquery.tokeninput' load='async' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'} +{footer_script require='jquery.tokeninput'} var tag_boxes_selector = ""; {foreach from=$elements item=element name=element} {if $smarty.foreach.element.first} @@ -13,22 +13,24 @@ prefix = ", "; {/if} tag_boxes_selector = tag_boxes_selector + prefix + "#tags-" + {$element.ID}; {/foreach} -{literal} -jQuery(document).ready(function() { - $(tag_boxes_selector).fcbkcomplete({ - json_url: "admin.php?fckb_tags=1", - cache: false, - filter_case: false, - filter_hide: true, - firstselected: true, - filter_selected: true, - maxitems: 100, - newel: true - }); - - $("a.preview-box").colorbox(); + +jQuery(document).ready(function() {ldelim} + jQuery(tag_boxes_selector).tokenInput( + [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}], + {ldelim} + hintText: '{'Type in a search term'|@translate}', + noResultsText: '{'No results'|@translate}', + searchingText: '{'Searching...'|@translate}', + newText: ' ({'new'|@translate})', + animateDropdown: false, + preventDuplicates: true, + allowCreation: true + } + ); + + jQuery("a.preview-box").colorbox(); }); -{/literal}{/footer_script} +{/footer_script} <h2>{'Batch Manager'|@translate}</h2> @@ -112,7 +114,7 @@ jQuery(document).ready(function() { <select id="tags-{$element.ID}" name="tags-{$element.ID}"> {foreach from=$element.TAGS item=tag} - <option value="{$tag.value}" class="selected">{$tag.key}</option> + <option value="{$tag.id}" class="selected">{$tag.name}</option> {/foreach} </select> diff --git a/admin/themes/default/template/picture_modify.tpl b/admin/themes/default/template/picture_modify.tpl index a5244f240..8cc01c43a 100644 --- a/admin/themes/default/template/picture_modify.tpl +++ b/admin/themes/default/template/picture_modify.tpl @@ -2,21 +2,23 @@ {include file='include/dbselect.inc.tpl'} {include file='include/datepicker.inc.tpl'} -{combine_script id='jquery.fcbkcomplete' load='async' require='jquery' path='themes/default/js/plugins/jquery.fcbkcomplete.js'} -{footer_script require='jquery.fcbkcomplete'}{literal} -jQuery(document).ready(function() { - jQuery("#tags").fcbkcomplete({ - json_url: "admin.php?fckb_tags=1", - cache: false, - filter_case: false, - filter_hide: true, - firstselected: true, - filter_selected: true, - maxitems: 100, - newel: true - }); +{combine_script id='jquery.tokeninput' load='async' require='jquery' path='themes/default/js/plugins/jquery.tokeninput.js'} +{footer_script require='jquery.tokeninput'} +jQuery(document).ready(function() {ldelim} + jQuery("#tags").tokenInput( + [{foreach from=$tags item=tag name=tags}{ldelim}"name":"{$tag.name}","id":"{$tag.id}"{rdelim}{if !$smarty.foreach.tags.last},{/if}{/foreach}], + {ldelim} + hintText: '{'Type in a search term'|@translate}', + noResultsText: '{'No results'|@translate}', + searchingText: '{'Searching...'|@translate}', + newText: ' ({'new'|@translate})', + animateDropdown: false, + preventDuplicates: true, + allowCreation: true + } + ); }); -{/literal}{/footer_script} +{/footer_script} {footer_script} pwg_initialization_datepicker("#date_creation_day", "#date_creation_month", "#date_creation_year", "#date_creation_linked_date", "#date_creation_action_set"); @@ -136,8 +138,8 @@ pwg_initialization_datepicker("#date_creation_day", "#date_creation_month", "#da <td><strong>{'Tags'|@translate}</strong></td> <td> <select id="tags" name="tags"> -{foreach from=$tags item=tag} - <option value="{$tag.value}" class="selected">{$tag.key}</option> +{foreach from=$tag_selection item=tag} + <option value="{$tag.id}" class="selected">{$tag.name}</option> {/foreach} </select> </td> diff --git a/admin/themes/default/theme.css b/admin/themes/default/theme.css index e3cab2eff..870844682 100644 --- a/admin/themes/default/theme.css +++ b/admin/themes/default/theme.css @@ -585,50 +585,6 @@ img.ui-datepicker-trigger { margin:-3px 5px 2px 5px; } -/* jQuery FCBKcomplete */ -/* TextboxList sample CSS */ -ul.holder { margin: 0; border: 1px solid #999; overflow: hidden; height: auto !important; height: 1%; padding: 4px 5px 0; } -*:first-child+html ul.holder { padding-bottom: 2px; } * html ul.holder { padding-bottom: 2px; } /* ie7 and below */ -ul.holder li { float: left; list-style-type: none; margin: 0 5px 4px 0; white-space:nowrap;} -ul.holder li.bit-box, ul.holder li.bit-input input { font: 11px "Lucida Grande", "Verdana"; } -ul.holder li.bit-box { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; border: 1px solid #CAD8F3; background: #DEE7F8; padding: 1px 5px 2px; } -ul.holder li.bit-box-focus { border-color: #598BEC; background: #598BEC; color: #fff; } -ul.holder li.bit-input input { width: auto; overflow:visible; margin: 0; border: 0px; outline: 0; padding: 3px 0px 2px; } /* no left/right padding here please */ -ul.holder li.bit-input input.smallinput { width: 20px; } - -/* Facebook demo CSS */ -#add { border: 1px solid #999; width: 550px; margin: 50px; padding: 20px 30px 10px; } -form ol li { list-style-type: none; } -form ol { font: 11px "Lucida Grande", "Verdana"; margin: 0; padding: 0; } -form ol li.input-text { margin-bottom: 10px; list-style-type: none; padding-bottom: 10px; } -form ol li.input-text label { font-weight: bold; cursor: pointer; display: block; font-size: 13px; margin-bottom: 10px; } -form ol li.input-text input { width: 500px; padding: 5px 5px 6px; font: 11px "Lucida Grande", "Verdana"; border: 1px solid #999; } -form ul.holder { width: 500px; } -form ul { margin: 0 !important } -ul.holder li.bit-box, #apple-list ul.holder li.bit-box { padding-right: 15px; position: relative; z-index:1000;} -#apple-list ul.holder li.bit-input { margin: 0; } -#apple-list ul.holder li.bit-input input.smallinput { width: 5px; } -ul.holder li.bit-hover { background: #BBCEF1; border: 1px solid #6D95E0; } -ul.holder li.bit-box-focus { border-color: #598BEC; background: #598BEC; color: #fff; } -ul.holder li.bit-box a.closebutton { position: absolute; right: 4px; top: 5px; display: block; width: 7px; height: 7px; font-size: 1px; background: url(icon/fcbkcomplete_close.gif); } -ul.holder li.bit-box a.closebutton:hover { background-position: 7px; } -ul.holder li.bit-box-focus a.closebutton, ul.holder li.bit-box-focus a.closebutton:hover { background-position: bottom; } - -/* Autocompleter */ - -.facebook-auto { display: none; position: absolute; width: 512px; background: #eee; } -.facebook-auto .default { padding: 5px 7px; border: 1px solid #ccc; border-width: 0 1px 1px;font-family:"Lucida Grande","Verdana"; font-size:11px; } -.facebook-auto ul { display: none; margin: 0; padding: 0; overflow: auto; position:absolute; z-index:9999} -.facebook-auto ul li { padding: 5px 12px; z-index: 1000; cursor: pointer; margin: 0; list-style-type: none; border: 1px solid #ccc; border-width: 0 1px 1px; font: 11px "Lucida Grande", "Verdana"; background-color: #eee } -.facebook-auto ul li em { font-weight: bold; font-style: normal; background: #ccc; } -.facebook-auto ul li.auto-focus { background: #4173CC; color: #fff; } -.facebook-auto ul li.auto-focus em { background: none; } -.deleted { background-color:#4173CC !important; color:#ffffff !important;} -.hidden { display:none;} - -#demo ul.holder li.bit-input input { padding: 2px 0 1px; border: 1px solid #999; } -.ie6fix {height:1px;width:1px; position:absolute;top:0px;left:0px;z-index:1;} - /* Add photos, direct mode */ #uploadBoxes P { margin:0; @@ -1045,6 +1001,24 @@ LEGEND { #batchManagerGlobal #applyFilterBlock {margin-top:20px;} #batchManagerGlobal .useFilterCheckbox {display:none} + +/* TokenInput (with Facebook style) */ +ul.token-input-list {overflow: hidden; height: auto !important; height: 1%;width: 400px;border: 1px solid #8496ba;cursor: text;font-size: 12px;font-family: Verdana;min-height: 1px;z-index: 999;margin: 0;padding: 0;background-color: #fff;list-style-type: none;clear: left;} +ul.token-input-list li input {border: 0;width: 100px;padding: 3px 8px;background-color: white;margin: 2px 0;-webkit-appearance: caret;} +li.token-input-token {overflow: hidden; height: auto !important; height: 15px;margin: 3px;padding: 1px 3px;background-color: #eff2f7;color: #000;cursor: default;border: 1px solid #ccd5e4;font-size: 11px;border-radius: 5px;-moz-border-radius: 5px;-webkit-border-radius: 5px;float: left;white-space: nowrap;} +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;margin: 0;padding: 0;list-style-type: none;width:10px;} +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: 1;} +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;margin: 0;list-style-type: none;} +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: normal;} +div.token-input-dropdown ul li.token-input-selected-dropdown-item {background-color: #3b5998;color: #fff;} + .warning { background:url(icon/warning.png) no-repeat top left; width: 130px; diff --git a/admin/themes/roma/theme.css b/admin/themes/roma/theme.css index 8830f5a12..3fb62a2df 100644 --- a/admin/themes/roma/theme.css +++ b/admin/themes/roma/theme.css @@ -245,3 +245,10 @@ display:block; height:85px; left:225px; position:relative; top:-42px; width:313p #batchManagerGlobal .thumbSelected {background-color:#555 !important} #batchManagerGlobal #selectedMessage {background-color:#555; color:#ddd;} +/* TokenInput (with Facebook style for ROMA) */ +ul.token-input-list {border-color:#666;background-color:#444;} +ul.token-input-list li input {background-color:#444;} +li.token-input-token span {color:#878787;} +div.token-input-dropdown {background-color:#eee;border-color:#666;} +div.token-input-dropdown ul li {background-color:#eee;} +div.token-input-dropdown ul li.token-input-selected-dropdown-item {background-color:#FF7800;} diff --git a/language/en_UK/admin.lang.php b/language/en_UK/admin.lang.php index cf1bc978a..0149292fb 100644 --- a/language/en_UK/admin.lang.php +++ b/language/en_UK/admin.lang.php @@ -429,6 +429,7 @@ $lang['Month'] = "Month"; $lang['Move albums'] = "Move albums"; $lang['Move'] = "Move"; $lang['Name'] = "Name"; +$lang['new'] = "new"; $lang['New name'] = "New name"; $lang['New parent album'] = "New parent album"; $lang['New photos added'] = "New photos added"; @@ -443,6 +444,7 @@ $lang['No photo in the current set.'] = 'No photo in the current set.'; $lang['No photo in this album'] = "No photo in this album"; $lang['No photo selected, %d photos in current set'] = 'No photo selected, %d photos in current set'; $lang['No photo selected, no action possible.'] = 'No photo selected, no action possible.'; +$lang['No results'] = "No results"; $lang['No user to send notifications by mail.'] = "No user to be notified by mail."; $lang['no write access'] = "no write access"; $lang['none'] = "none"; @@ -576,6 +578,7 @@ $lang['Save page visits by guests'] = "Record pages visited by guests"; $lang['Save page visits by users'] = "Record pages visited by users"; $lang['Save Settings'] = 'Save Settings'; $lang['Save to permalink history'] = "Save to permalinks history"; +$lang['Searching...'] = "Searching..."; $lang['Search for new images in the directories'] = "Search for new images in the directories"; $lang['Section'] = "Section"; $lang['See you soon,'] = "See you soon,"; @@ -691,6 +694,7 @@ $lang['Tools'] = "Tools"; $lang['total time'] = "total time"; $lang['Type here the author name'] = 'Type here the author name'; $lang['Type here the title'] = 'Type here the title'; +$lang['Type in a search term'] = "Type in a search term"; $lang['Unable to check for upgrade.'] = "Unable to check for upgrade."; $lang['Uncheck all'] = "Uncheck all"; $lang['Uninstall'] = "Uninstall"; diff --git a/language/fr_FR/admin.lang.php b/language/fr_FR/admin.lang.php index 466ff60a1..6a2882a1a 100644 --- a/language/fr_FR/admin.lang.php +++ b/language/fr_FR/admin.lang.php @@ -794,4 +794,8 @@ $lang['Do you want to activate anyway?'] = 'Voulez-vous l\'activer quand même?' $lang['THIS PLUGIN IS NOW PART OF PIWIGO CORE! DELETE IT NOW.'] = 'CE PLUGIN FAIT DÉSORMAIS PARTIE DU CORE DE PIWIGO! SUPPRIMEZ-LE.'; $lang['ERROR: THIS PLUGIN IS MISSING BUT IT IS INSTALLED! UNINSTALL IT NOW.'] = 'ERREUR: CE PLUGIN EST MANQUANT MAIS TOUJOURS INSTALLÉ! DÉSINSTALLEZ-LE.'; $lang['display'] = 'Afficher'; +$lang['No results'] = "Pas de résultat"; +$lang['Type in a search term'] = "Entrez un terme de recherche"; +$lang['Searching...'] = "Recherche..."; +$lang['new'] = "nouveau"; ?> diff --git a/themes/default/js/plugins/jquery.fcbkcomplete.js b/themes/default/js/plugins/jquery.fcbkcomplete.js deleted file mode 100644 index 3fb094305..000000000 --- a/themes/default/js/plugins/jquery.fcbkcomplete.js +++ /dev/null @@ -1,645 +0,0 @@ -/** - FCBKcomplete 2.7.5 - - Jquery version required: 1.2.x, 1.3.x, 1.4.x - - Based on TextboxList by Guillermo Rauch http://devthought.com/ - - Changelog: - - 2.00 new version of fcbkcomplete - - - 2.01 fixed bugs & added features - fixed filter bug for preadded items - focus on the input after selecting tag - the element removed pressing backspace when the element is selected - input tag in the control has a border in IE7 - added iterate over each match and apply the plugin separately - set focus on the input after selecting tag - - - 2.02 fixed fist element selected bug - fixed defaultfilter error bug - - - 2.5 removed selected="selected" attribute due ie bug - element search algorithm changed - better performance fix added - fixed many small bugs - onselect event added - onremove event added - - - 2.6 ie6/7 support fix added - added new public method addItem due request - added new options "firstselected" that you can set true/false to select first element on dropdown list - autoexpand input element added - removeItem bug fixed - and many more bug fixed - fixed public method to use it $("elem").trigger("addItem",[{"title": "test", "value": "test"}]); - -- 2.7 jquery 1.4 compability - item lock possability added by adding locked class to preadded option <option value="value" class="selected locked">text</option> - maximum item that can be added - -- 2.7.1 bug fixed - ajax delay added thanks to http://github.com/dolorian - -- 2.7.2 some minor bug fixed - minified version recompacted due some problems - -- 2.7.3 event call fixed thanks to William Parry <williamparry!at!gmail.com> - -- 2.7.4 standart event change call added on addItem, removeItem - preSet also check if item have "selected" attribute - addItem minor fix - -- 2.7.5 event call removeItem fixed - new public method destroy added needed to remove fcbkcomplete element from dome - - */ -/* Coded by: emposha <admin@emposha.com> */ -/* Copyright: Emposha.com <http://www.emposha.com> - Distributed under MIT - Keep this message! */ - -/** - * json_url - url to fetch json object - * cache - use cache - * height - maximum number of element shown before scroll will apear - * newel - show typed text like a element - * firstselected - automaticly select first element from dropdown - * filter_case - case sensitive filter - * filter_selected - filter selected items from list - * complete_text - text for complete page - * maxshownitems - maximum numbers that will be shown at dropdown list (less better performance) - * onselect - fire event on item select - * onremove - fire event on item remove - * maxitimes - maximum items that can be added - * delay - delay between ajax request (bigger delay, lower server time request) - * addontab - add first visible element on tab or enter hit - * attachto - after this element fcbkcomplete insert own elements - */ -jQuery(function($) { - $.fn.fcbkcomplete = function(opt) { - return this.each(function() { - function init() { - createFCBK(); - preSet(); - addInput(0); - } - - function createFCBK() { - element.hide(); - element.attr("multiple", "multiple"); - if (element.attr("name").indexOf("[]") == -1) { - element.attr("name", element.attr("name") + "[]"); - } - - holder = $(document.createElement("ul")); - holder.attr("class", "holder"); - - if (options.attachto) { - if (typeof(options.attachto) == "object") { - options.attachto.append(holder); - } - else { - $(options.attachto).append(holder); - } - - } - else { - element.after(holder); - } - - complete = $(document.createElement("div")); - complete.addClass("facebook-auto"); - complete.append('<div class="default">' + options.complete_text + "</div>"); - complete.hover(function() {options.complete_hover = 0;}, function() {options.complete_hover = 1;}); - - feed = $(document.createElement("ul")); - feed.attr("id", elemid + "_feed"); - - complete.prepend(feed); - holder.after(complete); - feed.css("width", complete.width()); - } - - function preSet() { - element.children("option").each(function(i, option) { - option = $(option); - if (option.hasClass("selected")) { - addItem(option.text(), option.val(), true, option.hasClass("locked")); - option.attr("selected", "selected"); - } - cache.push({ - key: option.text(), - value: option.val() - }); - search_string += "" + (cache.length - 1) + ":" + option.text() + ";"; - }); - } - - //public method to add new item - $(this).bind("addItem", - function(event, data) { - addItem(data.title, data.value, 0, 0, 0); - }); - - //public method to remove item - $(this).bind("removeItem", - function(event, data) { - var item = holder.children('li[rel=' + data.value + ']'); - if (item.length) { - removeItem(item); - } - }); - - //public method to remove item - $(this).bind("destroy", - function(event, data) { - holder.remove(); - complete.remove(); - element.show(); - }); - - function addItem(title, value, preadded, locked, focusme) { - if (!maxItems()) { - return false; - } - var li = document.createElement("li"); - var txt = document.createTextNode(title); - var aclose = document.createElement("a"); - var liclass = "bit-box" + (locked ? " locked": ""); - $(li).attr({ - "class": liclass, - "rel": value - }); - $(li).prepend(txt); - $(aclose).attr({ - "class": "closebutton", - "href": "#" - }); - - li.appendChild(aclose); - holder.append(li); - - $(aclose).click(function() { - removeItem($(this).parent("li")); - return false; - }); - - if (!preadded) { - $("#" + elemid + "_annoninput").remove(); - var _item; - addInput(focusme); - if (element.children("option[value=" + value + "]").length) { - _item = element.children("option[value=" + value + "]"); - _item.get(0).setAttribute("selected", "selected"); - _item.attr("selected", "selected"); - if (!_item.hasClass("selected")) { - _item.addClass("selected"); - } - } - else{ - var _item = $(document.createElement("option")); - _item.attr("value", value).get(0).setAttribute("selected", "selected"); - _item.attr("value", value).attr("selected", "selected"); - _item.attr("value", value).addClass("selected"); - _item.text(title); - element.append(_item); - } - if (options.onselect) { - funCall(options.onselect, _item) - } - element.change(); - } - holder.children("li.bit-box.deleted").removeClass("deleted"); - feed.hide(); - } - - function removeItem(item) { - - if (!item.hasClass('locked')) { - item.fadeOut("fast"); - if (options.onremove) { - var _item = element.children("option[value=" + item.attr("rel") + "]"); - funCall(options.onremove, _item) - } - element.children('option[value="' + item.attr("rel") + '"]').removeAttr("selected").removeClass("selected"); - item.remove(); - element.change(); - deleting = 0; - } - } - - function addInput(focusme) { - var li = $(document.createElement("li")); - var input = $(document.createElement("input")); - var getBoxTimeout = 0; - - li.attr({ - "class": "bit-input", - "id": elemid + "_annoninput" - }); - input.attr({ - "type": "text", - "class": "maininput", - "size": "1" - }); - holder.append(li.append(input)); - - input.focus(function() { - complete.fadeIn("fast"); - }); - - input.blur(function() { - if (options.complete_hover) { - complete.fadeOut("fast"); - } - else { - input.focus(); - } - }); - - holder.click(function() { - input.focus(); - if (feed.length && input.val().length) { - feed.show(); - } - else{ - feed.hide(); - complete.children(".default").show(); - } - }); - - input.keypress(function(event) { - if (event.keyCode == 13) { - return false; - } - //auto expand input - input.attr("size", input.val().length + 1); - }); - - input.keydown(function(event) { - //prevent to enter some bad chars when input is empty - if (event.keyCode == 191) { - event.preventDefault(); - return false; - } - }); - - input.keyup(function(event) { - var etext = xssPrevent(input.val()); - - if (event.keyCode == 8 && etext.length == 0) { - feed.hide(); - if (!holder.children("li.bit-box:last").hasClass('locked')) { - if (holder.children("li.bit-box.deleted").length == 0) { - holder.children("li.bit-box:last").addClass("deleted"); - return false; - } - else{ - if (deleting) { - return; - } - deleting = 1; - holder.children("li.bit-box.deleted").fadeOut("fast", - function() { - removeItem($(this)); - return false; - }); - } - } - } - - if (event.keyCode != 40 && event.keyCode != 38 && event.keyCode!=37 && event.keyCode!=39 && etext.length != 0) { - counter = 0; - - if (options.json_url) { - if (options.cache && json_cache) { - addMembers(etext); - bindEvents(); - } - else{ - getBoxTimeout++; - var getBoxTimeoutValue = getBoxTimeout; - setTimeout(function() { - if (getBoxTimeoutValue != getBoxTimeout) return; - $.getJSON(options.json_url, {tag: etext}, - function(data) { - addMembers(etext, data); - json_cache = true; - bindEvents(); - }); - }, - options.delay); - } - } - else{ - addMembers(etext); - bindEvents(); - } - complete.children(".default").hide(); - feed.show(); - } - }); - if (focusme) { - setTimeout(function() { - input.focus(); - complete.children(".default").show(); - }, - 1); - } - } - - function addMembers(etext, data) { - feed.html(''); - - if (!options.cache && data != null) { - cache = new Array(); - search_string = ""; - } - - addTextItem(etext); - - if (data != null && data.length) { - $.each(data, - function(i, val) { - cache.push({ - key: val.key, - value: val.value - }); - search_string += "" + (cache.length - 1) + ":" + val.key + ";"; - }); - } - - var maximum = options.maxshownitems < cache.length ? options.maxshownitems: cache.length; - var filter = "i"; - if (options.filter_case) { - filter = ""; - } - - var myregexp, - match; - try{ - myregexp = eval('/(?:^|;)\\s*(\\d+)\\s*:[^;]*?' + etext + '[^;]*/g' + filter); - match = myregexp.exec(search_string); - } - catch(ex) { - }; - - var content = ''; - while (match != null && maximum > 0) { - var id = match[1]; - var object = cache[id]; - if (options.filter_selected && element.children("option[value=" + object.value + "]").hasClass("selected")) { - //nothing here... - } - else{ - content += '<li rel="' + object.value + '">' + itemIllumination(object.key, etext) + '</li>'; - counter++; - maximum--; - } - match = myregexp.exec(search_string); - } - feed.append(content); - - if (options.firstselected) { - focuson = feed.children("li:visible:first"); - focuson.addClass("auto-focus"); - } - - if (counter > options.height) { - feed.css({ - "height": (options.height * 24) + "px", - "overflow": "auto" - }); - } - else{ - feed.css("height", "auto"); - } - } - - function itemIllumination(text, etext) { - if (options.filter_case) { - try{ - eval("var text = text.replace(/(.*)(" + etext + ")(.*)/gi,'$1<em>$2</em>$3');"); - } - catch(ex) { - }; - } - else{ - try{ - eval("var text = text.replace(/(.*)(" + etext.toLowerCase() + ")(.*)/gi,'$1<em>$2</em>$3');"); - } - catch(ex) { - }; - } - return text; - } - - function bindFeedEvent() { - feed.children("li").mouseover(function() { - feed.children("li").removeClass("auto-focus"); - $(this).addClass("auto-focus"); - focuson = $(this); - }); - - feed.children("li").mouseout(function() { - $(this).removeClass("auto-focus"); - focuson = null; - }); - } - - function removeFeedEvent() { - feed.children("li").unbind("mouseover"); - feed.children("li").unbind("mouseout"); - feed.mousemove(function() { - bindFeedEvent(); - feed.unbind("mousemove"); - }); - } - - function bindEvents() { - var maininput = $("#" + elemid + "_annoninput").children(".maininput"); - bindFeedEvent(); - feed.children("li").unbind("mousedown"); - feed.children("li").mousedown(function() { - var option = $(this); - addItem(option.text(), option.attr("rel"), 0, 0, 1); - feed.hide(); - complete.hide(); - }); - - maininput.unbind("keydown"); - maininput.keydown(function(event) { - if (event.keyCode == 191) { - event.preventDefault(); - return false; - } - - if (event.keyCode != 8) { - holder.children("li.bit-box.deleted").removeClass("deleted"); - } - - if ((event.keyCode == 13 || event.keyCode == 9) && checkFocusOn()) { - var option = focuson; - addItem(option.text(), option.attr("rel"), 0, 0, 1); - complete.hide(); - event.preventDefault(); - focuson = null; - return false; - } - - if ((event.keyCode == 13 || event.keyCode == 9) && !checkFocusOn()) { - if (options.newel) { - var value = xssPrevent($(this).val()); - addItem(value, value, 0, 0, 1); - complete.hide(); - event.preventDefault(); - focuson = null; - return false; - } - - if (options.addontab) { - focuson = feed.children("li:visible:first"); - var option = focuson; - addItem(option.text(), option.attr("rel"), 0, 0, 1); - complete.hide(); - event.preventDefault(); - focuson = null; - return false; - } - } - - if (event.keyCode == 40) { - removeFeedEvent(); - if (focuson == null || focuson.length == 0) { - focuson = feed.children("li:visible:first"); - feed.get(0).scrollTop = 0; - } - else{ - focuson.removeClass("auto-focus"); - focuson = focuson.nextAll("li:visible:first"); - var prev = parseInt(focuson.prevAll("li:visible").length, 10); - var next = parseInt(focuson.nextAll("li:visible").length, 10); - if ((prev > Math.round(options.height / 2) || next <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") { - feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2)); - } - } - feed.children("li").removeClass("auto-focus"); - focuson.addClass("auto-focus"); - } - if (event.keyCode == 38) { - removeFeedEvent(); - if (focuson == null || focuson.length == 0) { - focuson = feed.children("li:visible:last"); - feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (parseInt(feed.children("li:visible").length, 10) - Math.round(options.height / 2)); - } - else{ - focuson.removeClass("auto-focus"); - focuson = focuson.prevAll("li:visible:first"); - var prev = parseInt(focuson.prevAll("li:visible").length, 10); - var next = parseInt(focuson.nextAll("li:visible").length, 10); - if ((next > Math.round(options.height / 2) || prev <= Math.round(options.height / 2)) && typeof(focuson.get(0)) != "undefined") { - feed.get(0).scrollTop = parseInt(focuson.get(0).scrollHeight, 10) * (prev - Math.round(options.height / 2)); - } - } - feed.children("li").removeClass("auto-focus"); - focuson.addClass("auto-focus"); - } - }); - } - - function maxItems() { - if (options.maxitems != 0) { - if (holder.children("li.bit-box").length < options.maxitems) { - return true; - } - else{ - return false; - } - } - } - - function addTextItem(value) { - if (options.newel && maxItems()) { - feed.children("li[fckb=1]").remove(); - if (value.length == 0) { - return; - } - var li = $(document.createElement("li")); - li.attr({ - "rel": value, - "fckb": "1" - }).html(value); - feed.prepend(li); - counter++; - } - else{ - return; - } - } - - function funCall(func, item) { - var _object = ""; - for (i = 0; i < item.get(0).attributes.length; i++) { - if (item.get(0).attributes[i].nodeValue != null) { - _object += "\"_" + item.get(0).attributes[i].nodeName + "\": \"" + item.get(0).attributes[i].nodeValue + "\","; - } - } - _object = "{" + _object + " notinuse: 0}"; - func.call(func, _object); - } - - function checkFocusOn() { - if (focuson == null) { - return false; - } - if (focuson.length == 0) { - return false; - } - return true; - } - - function xssPrevent(string) { - string = string.replace(/[\"\'][\s]*javascript:(.*)[\"\']/g, "\"\""); - string = string.replace(/script(.*)/g, ""); - string = string.replace(/eval\((.*)\)/g, ""); - string = string.replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', ''); - return string; - } - - var options = $.extend({ - json_url: null, - cache: false, - height: "10", - newel: false, - addontab: false, - firstselected: false, - filter_case: false, - filter_selected: false, - complete_text: "Start to type...", - maxshownitems: 30, - maxitems: 10, - onselect: null, - onremove: null, - attachto: null, - delay: 350 - }, - opt); - - //system variables - var holder = null; - var feed = null; - var complete = null; - var counter = 0; - var cache = new Array(); - var json_cache = false; - var search_string = ""; - var focuson = null; - var deleting = 0; - var complete_hover = 1; - - var element = $(this); - var elemid = element.attr("id"); - init(); - - return this; - }); - }; -}); diff --git a/themes/default/js/plugins/jquery.tokeninput.js b/themes/default/js/plugins/jquery.tokeninput.js new file mode 100644 index 000000000..0c8662b3b --- /dev/null +++ b/themes/default/js/plugins/jquery.tokeninput.js @@ -0,0 +1,777 @@ +/** + DON'T MAKE AUTOMATIC UPGRADE + This is a merged version of : + + https://github.com/gr2m/jquery-tokeninput/ + + https://github.com/mistic100/jquery-tokeninput/ +*/ + +/* + * jQuery Plugin: Tokenizing Autocomplete Text Entry + * Version 1.4.2 + * + * Copyright (c) 2009 James Smith (http://loopj.com) + * Licensed jointly under the GPL and MIT licenses, + * choose which one suits your project best! + * + */ + +(function ($) { +// Default settings +var DEFAULT_SETTINGS = { + hintText: "Type in a search term", + noResultsText: "No results", + searchingText: "Searching...", + newText: "(new)", + deleteText: "×", + searchDelay: 300, + minChars: 1, + tokenLimit: null, + jsonContainer: null, + method: "GET", + contentType: "json", + queryParam: "q", + tokenDelimiter: ",", + preventDuplicates: false, + prePopulate: null, + processPrePopulate: false, + animateDropdown: true, + onResult: null, + onAdd: null, + onDelete: null, + allowCreation: false +}; + +// Default classes to use when theming +var DEFAULT_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" +}; + +// Input box position "enum" +var POSITION = { + BEFORE: 0, + AFTER: 1, + END: 2 +}; + +// Keys "enum" +var KEY = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + ESCAPE: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + NUMPAD_ENTER: 108, + COMMA: 188 +}; + + +// Expose the .tokenInput function to jQuery as a plugin +$.fn.tokenInput = function (url_or_data, options) { + var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); + + return this.each(function () { + new $.TokenList(this, url_or_data, settings); + }); +}; + + +// TokenList class for each input +$.TokenList = function (input, url_or_data, settings) { + // + // Initialization + // + + // Configure the data source + if(typeof(url_or_data) === "string") { + // Set the url to query against + settings.url = url_or_data; + + // Make a smart guess about cross-domain if it wasn't explicitly specified + if(settings.crossDomain === undefined) { + if(settings.url.indexOf("://") === -1) { + settings.crossDomain = false; + } else { + settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); + } + } + } else if(typeof(url_or_data) === "object") { + // Set the local data to search through + settings.local_data = url_or_data; + } + + // Build class names + if(settings.classes) { + // Use custom class names + settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes); + } else if(settings.theme) { + // Use theme-suffixed default class names + settings.classes = {}; + $.each(DEFAULT_CLASSES, function(key, value) { + settings.classes[key] = value + "-" + settings.theme; + }); + } else { + settings.classes = DEFAULT_CLASSES; + } + + + // 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, old vals + var timeout; + var input_val; + + // Create a new text input an attach keyup events + var input_box = $("<input type=\"text\" autocomplete=\"off\">") + .css({ + outline: "none" + }) + .focus(function () { + if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { + show_dropdown_hint(); + } + }) + .blur(function () { + hide_dropdown(); + }) + .bind("keyup keydown blur update", resize_input) + .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();}, 5); + } + break; + + case KEY.TAB: + case KEY.ENTER: + case KEY.NUMPAD_ENTER: + case KEY.COMMA: + if(selected_dropdown_item) { + add_token($(selected_dropdown_item)); + return false; + } + break; + + case KEY.ESCAPE: + hide_dropdown(); + return true; + + default: + if(String.fromCharCode(event.which)) { + // set a timeout just long enough to let this function finish. + setTimeout(function(){do_search();}, 5); + } + break; + } + }); + + if ($(input).get(0).tagName == 'SELECT') { + // Create a new input to store selected tokens, original will be delete later + var hidden_input = $("<input type=\"text\" name=\"" + $(input).attr('name') + "\" autocomplete=\"off\">") + .hide() + .val("") + .focus(function () { + input_box.focus(); + }) + .blur(function () { + input_box.blur(); + }) + .insertBefore(input); + } else { + // Keep a reference to the original input box + var hidden_input = $(input) + .hide() + .val("") + .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_token_index = 0; + var selected_dropdown_item = null; + + // The list to store the token items in + var token_list = $("<ul />") + .addClass(settings.classes.tokenList) + .click(function (event) { + var li = $(event.target).closest("li"); + if(li && li.get(0) && $.data(li.get(0), "tokeninput")) { + toggle_select_token(li); + } else { + // Deselect selected token + if(selected_token) { + deselect_token($(selected_token), POSITION.END); + } + + // Focus input box + input_box.focus(); + } + }) + .mouseover(function (event) { + var li = $(event.target).closest("li"); + if(li && selected_token !== this) { + li.addClass(settings.classes.highlightedToken); + } + }) + .mouseout(function (event) { + var li = $(event.target).closest("li"); + if(li && selected_token !== this) { + li.removeClass(settings.classes.highlightedToken); + } + }) + .insertBefore(hidden_input); + + // The token holding the input box + var input_token = $("<li />") + .addClass(settings.classes.inputToken) + .appendTo(token_list) + .append(input_box); + + // The list to store the dropdown items in + var dropdown = $("<div>") + .addClass(settings.classes.dropdown) + .appendTo("body") + .hide(); + + // Magic element to help us resize the text input + var input_resizer = $("<tester/>") + .insertAfter(input_box) + .css({ + position: "absolute", + top: -9999, + left: -9999, + width: "auto", + fontSize: input_box.css("fontSize"), + fontFamily: input_box.css("fontFamily"), + fontWeight: input_box.css("fontWeight"), + letterSpacing: input_box.css("letterSpacing"), + whiteSpace: "nowrap" + }); + + // Pre-populate list if items exist + hidden_input.val(""); + var li_data = settings.prePopulate || hidden_input.data("pre"); + if(settings.processPrePopulate && $.isFunction(settings.onResult)) { + li_data = settings.onResult.call(hidden_input, li_data); + } + if(li_data && li_data.length) { + $.each(li_data, function (index, value) { + insert_token(value.id, value.name); + }); + } + + // Pre-populate from SELECT options + if ($(input).get(0).tagName == 'SELECT') { + $(input).children('option').each(function () { + insert_token($(this).attr('value'), $(this).text()); + }); + } + + + + // + // Private functions + // + + function resize_input() { + if(input_val === (input_val = input_box.val())) {return;} + + // Enter new content into resizer and resize input accordingly + var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>'); + input_resizer.html(escaped); + input_box.width(input_resizer.width() + 30); + } + + function is_printable_character(keycode) { + return ((keycode >= 48 && keycode <= 90) || // 0-1a-z + (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * . + (keycode >= 186 && keycode <= 192) || // ; = , - . / ^ + (keycode >= 219 && keycode <= 222)); // ( \ ) ' + } + + // 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>" + settings.deleteText + "</span>") + .addClass(settings.classes.tokenDelete) + .appendTo(this_token) + .click(function () { + delete_token($(this).parent()); + return false; + }); + + // Store data on the token + var token_data = {"id": id, "name": value}; + $.data(this_token.get(0), "tokeninput", token_data); + + // Save this token for duplicate checking + saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index)); + selected_token_index++; + + // Update the hidden input + var token_ids = $.map(saved_tokens, function (el) { + return el.id; + }); + hidden_input.val(token_ids.join(settings.tokenDelimiter)); + + token_count += 1; + + return this_token; + } + + // Add a token to the token list based on user input + function add_token (item) { + var li_data = $.data(item.get(0), "tokeninput"); + var callback = settings.onAdd; + + // See if the token already exists and select it if we don't want duplicates + if(token_count > 0 && settings.preventDuplicates) { + var found_existing_token = null; + token_list.children().each(function () { + var existing_token = $(this); + var existing_data = $.data(existing_token.get(0), "tokeninput"); + if(existing_data && existing_data.id === li_data.id) { + found_existing_token = existing_token; + return false; + } + }); + + if(found_existing_token) { + select_token(found_existing_token); + input_token.insertAfter(found_existing_token); + input_box.focus(); + return; + } + } + + // Insert the new tokens + insert_token(li_data.id, li_data.name); + + // Check the token limit + if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) { + input_box.hide(); + hide_dropdown(); + return; + } else { + input_box.focus(); + } + + // Clear input box + input_box.val(""); + + // Don't show the help dropdown, they've got the idea + hide_dropdown(); + + // Execute the onAdd callback if defined + if($.isFunction(callback)) { + callback.call(hidden_input,li_data); + } + } + + // 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); + selected_token_index--; + } else if(position === POSITION.AFTER) { + input_token.insertAfter(token); + selected_token_index++; + } else { + input_token.appendTo(token_list); + selected_token_index = token_count; + } + + // 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) { + var previous_selected_token = selected_token; + + if(selected_token) { + deselect_token($(selected_token), POSITION.END); + } + + if(previous_selected_token === token.get(0)) { + deselect_token(token, POSITION.END); + } else { + 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"); + var callback = settings.onDelete; + + var index = token.prevAll().length; + if(index > selected_token_index) index--; + + // Delete the token + token.remove(); + selected_token = null; + + // Show the input box and give it focus again + input_box.focus(); + + // Remove this token from the saved list + saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1)); + if(index < selected_token_index) selected_token_index--; + + // Update the hidden input + var token_ids = $.map(saved_tokens, function (el) { + return el.id; + }); + hidden_input.val(token_ids.join(settings.tokenDelimiter)); + + token_count -= 1; + + if(settings.tokenLimit !== null) { + input_box + .show() + .val("") + .focus(); + } + + // Execute the onDelete callback if defined + if($.isFunction(callback)) { + callback.call(hidden_input,token_data); + } + } + + // Hide and clear the results dropdown + function hide_dropdown () { + dropdown.hide().empty(); + selected_dropdown_item = null; + } + + function show_dropdown() { + dropdown + .css({ + position: "absolute", + top: $(token_list).offset().top + $(token_list).outerHeight(), + left: $(token_list).offset().left, + zindex: 999 + }) + .show(); + } + + function show_dropdown_searching () { + if(settings.searchingText) { + dropdown.html("<p>"+settings.searchingText+"</p>"); + show_dropdown(); + } + } + + function show_dropdown_hint () { + if(settings.hintText) { + dropdown.html("<p>"+settings.hintText+"</p>"); + show_dropdown(); + } + } + + // 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 && results.length) { + dropdown.empty(); + var dropdown_ul = $("<ul>") + .appendTo(dropdown) + .mouseover(function (event) { + select_dropdown_item($(event.target).closest("li")); + }) + .mousedown(function (event) { + add_token($(event.target).closest("li")); + return false; + }) + .hide(); + + $.each(results, function(index, value) { + var this_li = $("<li>" + highlight_term(value.name, query) + "</li>") + .appendTo(dropdown_ul); + + if(index % 2) { + this_li.addClass(settings.classes.dropdownItem); + } else { + this_li.addClass(settings.classes.dropdownItem2); + } + + if(index === 0) { + select_dropdown_item(this_li); + } + + $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name}); + }); + + show_dropdown(); + + if(settings.animateDropdown) { + dropdown_ul.slideDown("fast"); + } else { + dropdown_ul.show(); + } + } else { + if(settings.noResultsText) { + dropdown.html("<p>"+settings.noResultsText+"</p>"); + show_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() { + 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(); + 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 { + // Are we doing an ajax search or local data search? + if(settings.url) { + // Extract exisiting get params + var ajax_params = {}; + ajax_params.data = {}; + if(settings.url.indexOf("?") > -1) { + var parts = settings.url.split("?"); + ajax_params.url = parts[0]; + + var param_array = parts[1].split("&"); + $.each(param_array, function (index, value) { + var kv = value.split("="); + ajax_params.data[kv[0]] = kv[1]; + }); + } else { + ajax_params.url = settings.url; + } + + // Prepare the request + ajax_params.data[settings.queryParam] = query; + ajax_params.type = settings.method; + ajax_params.dataType = settings.contentType; + if(settings.crossDomain) { + ajax_params.dataType = "jsonp"; + } + + // Attach the success callback + ajax_params.success = function(results) { + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + + if(settings.allowCreation) { + results.push({name: input_box.val() + settings.newText, id: input_box.val()}); + } + cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + + // only populate the dropdown if the results are associated with the active search query + if(input_box.val().toLowerCase() === query) { + populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); + } + }; + + // Make the request + $.ajax(ajax_params); + } else if(settings.local_data) { + // Do the search through local data + var results = $.grep(settings.local_data, function (row) { + return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1; + }); + + if($.isFunction(settings.onResult)) { + results = settings.onResult.call(hidden_input, results); + } + + if(settings.allowCreation) { + results.push({name: input_box.val() + settings.newText, id: input_box.val()}); + } + + cache.add(query, results); + + populate_dropdown(query, results); + } + } + } + + if ($(input).get(0).tagName == 'SELECT') { + $(input).remove(); + } +}; + +// Really basic cache for the results +$.TokenList.Cache = function (options) { + var settings = $.extend({ + max_size: 500 + }, 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 += 1; + } + + data[query] = results; + }; + + this.get = function (query) { + return data[query]; + }; +}; +}(jQuery)); |