├── cakefile ├── LICENSE ├── css └── bootstrap-tags.css ├── README.md ├── tag-demo.html ├── src └── bootstrap-tags.coffee └── lib └── bootstrap-tags.js /cakefile: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | 3 | {print} = require 'sys' 4 | {spawn} = require 'child_process' 5 | 6 | build = (callback) -> 7 | coffee = spawn 'coffee', ['-c', '-o', 'lib', 'src'] 8 | coffee.stderr.on 'data', (data) -> 9 | process.stderr.write data.toString() 10 | coffee.stdout.on 'data', (data) -> 11 | print data.toString() 12 | coffee.on 'exit', (code) -> 13 | callback?() if code is 0 14 | 15 | task 'build', 'Build lib/ from src/', -> 16 | build() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Max Lahey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /css/bootstrap-tags.css: -------------------------------------------------------------------------------- 1 | /* bootstrap-tags styles */ 2 | 3 | .tag-list { 4 | width: 280px; 5 | height: 26px; 6 | left:2px; 7 | top:2px; 8 | position:relative; 9 | } 10 | .tag-data { 11 | display:none; 12 | } 13 | .tags-input { 14 | width:100%; 15 | height:100% !important; 16 | margin:0; 17 | padding-bottom:0 !important; 18 | font-size:12px !important; 19 | } 20 | .tags { 21 | width:inherit; 22 | height:0; 23 | position:absolute; 24 | padding:0; 25 | margin:0; 26 | } 27 | .tag { 28 | padding: 1px 3px; 29 | margin:1px; 30 | float:left; 31 | } 32 | .tag a { 33 | color: #bbb; 34 | cursor:pointer; 35 | opacity: .5; 36 | } 37 | ul.tags-suggestion-list { 38 | width:100%; 39 | height:auto; 40 | list-style:none; 41 | margin:0; 42 | position:absolute; 43 | z-index:2; 44 | max-height:160px; 45 | overflow: scroll; 46 | } 47 | li.tags-suggestion { 48 | padding:3px 20px; 49 | height:auto; 50 | } 51 | li.tags-suggestion-highlighted { 52 | color: white; 53 | text-decoration: none; 54 | background-color: #0081C2; 55 | background-image: -moz-linear-gradient(top, #08C, #0077B3); 56 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08C), to(#0077B3)); 57 | background-image: -webkit-linear-gradient(top, #08C, #0077B3); 58 | background-image: -o-linear-gradient(top, #08C, #0077B3); 59 | background-image: linear-gradient(to bottom, #08C, #0077B3); 60 | background-repeat: repeat-x; 61 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); 62 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boostrap Tags 2 | 3 | Bootstrap tags is a lightweight jQuery plugin meant to extend the Twitter Bootstrap UI to include tagging. 4 | 5 | ## Demo 6 | [http://jsfiddle.net/cjFZW/2/](http://jsfiddle.net/cjFZW/2/) 7 | Note: for whatever reason the width and height of the text boxes in jsFiddle aren't cooporating, so please imagine that the box isn't resizing. 8 | 9 | ## Features 10 | - Autosuggest (for typing or activated by pressing the down key when empty) 11 | - Bootstrap Popovers (for extended information on a tag) 12 | - Exclusions (denial of a specified list) 13 | - Filters (allowance of only a specified list) 14 | - Placeholder prompts 15 | - Uses bootstrap button-[type] class styling (customizing your bootstrap will change tag styles accordingly) 16 | - Extendable with custom functions (eg, on successful tag addition, key presses, exclusions) 17 | - Not tested on many browsers yet. Uses some HTML5 stuff, so no promises 18 | 19 | ## Implementation 20 |
21 | 30 | 31 | ## Documentation 32 | 33 | ### Settings 34 | 35 | - `suggestions`: list of autosuggest terms 36 | - `restrictTo`: list of allowed tags 37 | - `exclude`: list of disallowed tags 38 | - `displayPopovers`: boolean 39 | - `tagClass`: string for what class the tag div will have for styling 40 | - `promptText`: placeholder string when there are no tags and nothing typed in 41 | 42 | See Implementation above for example 43 | 44 | 45 | ### Overrideable functions 46 | If you want to override any of the following functions, pass it as an option in the jQuery initialization. 47 | 48 | - `whenAddingTag (tag:string)` : anything external you'd like to do with the tag 49 | - `definePopover (tag:string)` : must return the popover content for the tag that is being added. (eg "Content for [tag]") 50 | - `excludes (tag:string)` : returns true if you want the tag to be excluded, false if allowed 51 | - `pressedReturn (e:triggering event)` 52 | - `pressedDelete (e:triggering event)` 53 | - `pressedDown (e:triggering event)` 54 | - `pressedUp (e:triggering event)` 55 | 56 | Example: 57 | 58 | pressedUp = function(e) { console.log('pressed up'); }; 59 | whenAddingTag = function (tag) { 60 | console.log(tag); 61 | // maybe fetch some content for the tag popover (can be HTML) 62 | }; 63 | excludes = function (tag) { 64 | // return false if this tagger does *not* exclude 65 | // -> returns true if tagger should exclude this tag 66 | // --> this will exclude anything with ! 67 | return (tag.indexOf("!") != -1); 68 | }; 69 | $('#two').tags( { 70 | suggestions : ["there", "were", "some", "suggested", "terms", "super", "secret", "stuff"], 71 | restrictTo : ["restrict", "to", "these"], 72 | whenAddingTag : whenAddingTag, 73 | pressedUp : pressedUp, 74 | tagClass : 'btn-warning' } 75 | ); -------------------------------------------------------------------------------- /tag-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tag Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Bootstrap Tags Demo

15 | The following tag-list is unfiltered, with the suggestions "here", "are", "some", "suggestions". It is set to exclude a few terms: "excuse", "my", "vulgarity" and anything with an exclamation point. Additionally, this tag-list has popovers enabled. Should you want to programmatically define what the popover content is for a given tag, the definePopover(tag) method can be overwritten. Tags are defined in jQuery statement. This is the default color 16 | 17 |
18 | 19 | The second tag-list has a number of suggested terms and a couple restricted terms ("restrict", "to", "these"). 20 |
By default, all suggested terms are allowed, so if a user deletes one, it can be repopulated. Popovers are disabled. This tag-list substitutes in a custom function (whenAddingTag) that logs the tag to the console if it is successfully added -- a more practical use would be to fetch popover content for the tag that was added with setPopover(tag, content) in an ajax callback. Tags are defined from .tag-data div. This example overrides default display class (btn-info) with .btn-warning. NB: As shown, the width of the containing (.tag-list) div is inherited by most children elements. 21 | 22 |
tag5,tag6,tag7,tag8
23 | 24 | Here are some other colors (pulled right from bootstrap styling, so if you change the bootstrap colors, these will all change accordingly) 25 |
26 |
27 |
28 | 29 | 68 |
69 | 70 | 71 | -------------------------------------------------------------------------------- /src/bootstrap-tags.coffee: -------------------------------------------------------------------------------- 1 | # Bootstrap Tags 2 | # Max Lahey 3 | # November, 2012 4 | 5 | jQuery -> 6 | $.tags = (element, options) -> 7 | 8 | @suggestions = (if options.suggestions? then options.suggestions else []) 9 | @restrictTo = (if options.restrictTo? then options.restrictTo.concat @suggestions else false) 10 | @exclude = (if options.excludeList? then options.excludeList else false) 11 | @displayPopovers = (if options.popovers? then true else options.popoverData?) 12 | @tagClass = (if options.tagClass? then options.tagClass else 'btn-info') 13 | @promptText = (if options.promptText? then options.promptText else 'Enter tags...') 14 | 15 | # override-able functions 16 | @whenAddingTag = (if options.whenAddingTag? then options.whenAddingTag else (tag) -> ) 17 | @definePopover = (if options.definePopover then options.definePopover else (tag) -> "associated content for \""+tag+"\"" ) 18 | @excludes = (if options.excludes then options.excludes else -> false) 19 | # override-able key press functions 20 | @pressedReturn = (if options.pressedReturn? then options.pressedReturn else (e) -> ) 21 | @pressedDelete = (if options.pressedDelete? then options.pressedDelete else (e) -> ) 22 | @pressedDown = (if options.pressedDown? then options.pressedDown else (e) -> ) 23 | @pressedUp = (if options.pressedUp? then options.pressedUp else (e) -> ) 24 | 25 | # hang on to so we know what we are 26 | @$element = $ element 27 | 28 | # tagsArray is list of tags 29 | if options.tagData? 30 | @tagsArray = options.tagData 31 | else 32 | tagData = $('.tag-data', @$element).html() 33 | @tagsArray = (if tagData? then tagData.split ',' else []) 34 | 35 | if @displayPopovers 36 | @popoverArray = options.popoverData 37 | 38 | # add/remove methods 39 | @removeTagClicked = (e) => # clicked remove tag anchor 40 | if e.currentTarget.tagName == "A" 41 | @removeTag e.currentTarget.previousSibling.textContent 42 | $(e.currentTarget.parentNode).remove() 43 | 44 | @removeLastTag = => # pressed delete on empty string in input. 45 | el = $('.tag', @$element).last() 46 | el.remove() 47 | @removeTag @tagsArray[@tagsArray.length-1] 48 | 49 | @removeTag = (tag) => # removes specified tag 50 | if @tagsArray.indexOf(tag) > -1 51 | if @displayPopovers 52 | @popoverArray.splice(@tagsArray.indexOf(tag),1) 53 | @tagsArray.splice(@tagsArray.indexOf(tag), 1) 54 | @renderTags() 55 | 56 | @addTag = (tag) => # adds specified tag 57 | if (@restrictTo == false or @restrictTo.indexOf(tag) != -1) and @tagsArray.indexOf(tag) < 0 and tag.length > 0 and (@exclude == false || @exclude.indexOf(tag) == -1) and !@excludes(tag) 58 | @whenAddingTag(tag) 59 | if @displayPopovers 60 | associatedContent = @definePopover(tag) 61 | @popoverArray.push associatedContent 62 | @tagsArray.push tag 63 | @renderTags() 64 | 65 | @addTagWithContent = (tag, content) => # adds tag with associated popover content 66 | if (@restrictTo == false or @restrictTo.indexOf(tag) != -1) and @tagsArray.indexOf(tag) < 0 and tag.length > 0 67 | @whenAddingTag(tag) 68 | @tagsArray.push tag 69 | @popoverArray.push content 70 | @renderTags() 71 | 72 | @renameTag = (name, newName) => 73 | @tagsArray[@tagsArray.indexOf name] = newName 74 | @renderTags() 75 | 76 | @setPopover = (tag, popoverContent) => 77 | @popoverArray[@tagsArray.indexOf tag] = popoverContent 78 | @renderTags() 79 | 80 | # toggles remove button opacity for a tag when moused over or out 81 | @toggleCloseColor = (e) -> 82 | tagAnchor = $ e.currentTarget 83 | opacity = tagAnchor.css('opacity') 84 | opacity = (if opacity < 0.8 then 1.0 else 0.6) 85 | tagAnchor.css opacity:opacity 86 | 87 | # Key handlers 88 | @keyDownHandler = (e) => 89 | k = (if e.keyCode? then e.keyCode else e.which) 90 | switch k 91 | when 13 # enter (submit tag or selected suggestion) 92 | @pressedReturn(e) 93 | tag = e.target.value 94 | if @suggestedIndex != -1 95 | tag = @suggestionList[@suggestedIndex] 96 | @addTag tag 97 | e.target.value = '' 98 | @renderTags() 99 | @hideSuggestions() 100 | when 46, 8 # delete 101 | @pressedDelete(e) 102 | if e.target.value == '' 103 | @removeLastTag() 104 | if e.target.value.length == 1 # is one (which will be deleted after JS processing) 105 | @hideSuggestions() 106 | when 40 # down 107 | @pressedDown(e) 108 | if @input.val() == '' and (@suggestedIndex == -1 || !@suggestedIndex?) 109 | @makeSuggestions e, true 110 | numSuggestions = @suggestionList.length 111 | @suggestedIndex = (if @suggestedIndex < numSuggestions-1 then @suggestedIndex+1 else numSuggestions-1) 112 | @selectSuggested @suggestedIndex 113 | @scrollSuggested @suggestedIndex if @suggestedIndex >= 0 114 | when 38 # up 115 | @pressedUp(e) 116 | @suggestedIndex = (if @suggestedIndex > 0 then @suggestedIndex-1 else 0) 117 | @selectSuggested @suggestedIndex 118 | @scrollSuggested @suggestedIndex if @suggestedIndex >= 0 119 | when 9, 27 # tab, escape 120 | @hideSuggestions() 121 | @suggestedIndex = -1 122 | else 123 | 124 | # makeSuggestions creates auto suggestions that match the value in the input 125 | # if overrideLengthCheck is set to true, then if the input value is empty (''), return all possible suggestions 126 | @makeSuggestions = (e, overrideLengthCheck) => 127 | val = (if e.target.value? then e.target.value else e.target.textContent) 128 | @suggestedIndex = -1 129 | @$suggestionList.html '' 130 | @suggestionList = [] 131 | $.each @suggestions, (i, suggestion) => 132 | if @tagsArray.indexOf(suggestion) < 0 and suggestion.substring(0, val.length) == val and (val.length > 0 or overrideLengthCheck) 133 | @$suggestionList.append '
  • '+suggestion+'
  • ' 134 | @suggestionList.push suggestion 135 | $('.tags-suggestion', @$element).mouseover @selectSuggestedMouseOver 136 | $('.tags-suggestion', @$element).click @suggestedClicked 137 | if @suggestionList.length > 0 138 | @showSuggestions() 139 | else 140 | @hideSuggestions() # so the rounded parts on top & bottom of dropdown do not show up 141 | 142 | @suggestedClicked = (e) => # clicked on a suggestion 143 | tag = e.target.textContent 144 | if @suggestedIndex != -1 145 | tag = @suggestionList[@suggestedIndex] 146 | @addTag tag 147 | @input.val '' 148 | @makeSuggestions e, false 149 | @input.focus() # return focus to input so user can continue typing 150 | @hideSuggestions() 151 | 152 | @keyUpHandler = (e) => 153 | k = (if e.keyCode? then e.keyCode else e.which) 154 | if k != 40 and k != 38 and k != 27 155 | @makeSuggestions e, false 156 | 157 | # display methods 158 | @hideSuggestions = => 159 | $('.tags-suggestion-list', @$element).css display: "none" 160 | 161 | @showSuggestions = => 162 | $('.tags-suggestion-list', @$element).css display: "block" 163 | 164 | @selectSuggestedMouseOver = (e) => 165 | $('.tags-suggestion').removeClass('tags-suggestion-highlighted') 166 | $(e.target).addClass('tags-suggestion-highlighted') 167 | $(e.target).mouseout @selectSuggestedMousedOut 168 | @suggestedIndex = $('.tags-suggestion', @$element).index($(e.target)) 169 | 170 | @selectSuggestedMousedOut = (e) => 171 | $(e.target).removeClass('tags-suggestion-highlighted') 172 | 173 | @selectSuggested = (i) => # shows the provided index of suggestion list for this element as highlighted (exclusion of others) 174 | $('.tags-suggestion').removeClass('tags-suggestion-highlighted') 175 | tagElement = $('.tags-suggestion', @$element).eq(i) 176 | tagElement.addClass 'tags-suggestion-highlighted' 177 | 178 | @scrollSuggested = (i) => 179 | tagElement = $('.tags-suggestion', @$element).eq i 180 | topElement = $('.tags-suggestion', @$element).eq 0 181 | pos = tagElement.position() 182 | topPos = topElement.position() 183 | #if pos? and topPos? 184 | $('.tags-suggestion-list', @$element).scrollTop pos.top - topPos.top 185 | 186 | # adjust padding on input so that what user types shows up next to last tag (or on new line if insufficient space) 187 | @adjustInputPosition = => 188 | tagElement = $('.tag', @$element).last() 189 | tagPosition = tagElement.position() 190 | pLeft = if tagPosition? then tagPosition.left + tagElement.outerWidth(true) else 0 191 | pTop = if tagPosition? then tagPosition.top else 0 192 | $('.tags-input', @$element).css 193 | paddingLeft : pLeft 194 | paddingTop : pTop 195 | pBottom = if tagPosition? then tagPosition.top + tagElement.outerHeight(true) else 22 196 | @$element.css height : pBottom 197 | 198 | @renderTags = => 199 | tagList = $('.tags',@$element) 200 | tagList.html('') 201 | @input.attr 'placeholder', (if @tagsArray.length == 0 then @promptText else '') 202 | $.each @tagsArray, (i, tag) => 203 | tag = $(@formatTag i, tag) 204 | $('a', tag).click @removeTagClicked 205 | $('a', tag).mouseover @toggleCloseColor 206 | $('a', tag).mouseout @toggleCloseColor 207 | if @displayPopovers 208 | $('span', tag).mouseover -> 209 | tag.popover('show') 210 | $('span', tag).mouseout -> 211 | tag.popover('hide') 212 | tagList.append tag 213 | @adjustInputPosition() 214 | 215 | @formatTag = (i, tag) => 216 | if @displayPopovers == true # then attach popover data 217 | popoverContent = @popoverArray[@tagsArray.indexOf tag] 218 | "
    "+tag+"
    " 219 | else 220 | "
    "+tag+"
    " 221 | 222 | @addDocumentListeners = => 223 | $(document).mouseup (e) => 224 | container = $('.tags-suggestion-list', @$element) 225 | if container.has(e.target).length == 0 226 | @hideSuggestions() 227 | 228 | @init = -> 229 | # build out tags from specified markup 230 | @input = $ "" 231 | @input.keydown @keyDownHandler 232 | @input.keyup @keyUpHandler 233 | @$element.append @input 234 | @$suggestionList = $ '' 235 | @$element.append @$suggestionList 236 | # show it 237 | @renderTags() 238 | 239 | @addDocumentListeners() 240 | 241 | @init() 242 | 243 | @ 244 | 245 | $.fn.tags = (options) -> 246 | return this.each -> 247 | tags = new $.tags this, options -------------------------------------------------------------------------------- /lib/bootstrap-tags.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.4.0 2 | (function() { 3 | 4 | jQuery(function() { 5 | $.tags = function(element, options) { 6 | var tagData, 7 | _this = this; 8 | this.suggestions = (options.suggestions != null ? options.suggestions : []); 9 | this.restrictTo = (options.restrictTo != null ? options.restrictTo.concat(this.suggestions) : false); 10 | this.exclude = (options.excludeList != null ? options.excludeList : false); 11 | this.displayPopovers = (options.popovers != null ? true : options.popoverData != null); 12 | this.tagClass = (options.tagClass != null ? options.tagClass : 'btn-info'); 13 | this.promptText = (options.promptText != null ? options.promptText : 'Enter tags...'); 14 | this.whenAddingTag = (options.whenAddingTag != null ? options.whenAddingTag : function(tag) {}); 15 | this.definePopover = (options.definePopover ? options.definePopover : function(tag) { 16 | return "associated content for \"" + tag + "\""; 17 | }); 18 | this.excludes = (options.excludes ? options.excludes : function() { 19 | return false; 20 | }); 21 | this.pressedReturn = (options.pressedReturn != null ? options.pressedReturn : function(e) {}); 22 | this.pressedDelete = (options.pressedDelete != null ? options.pressedDelete : function(e) {}); 23 | this.pressedDown = (options.pressedDown != null ? options.pressedDown : function(e) {}); 24 | this.pressedUp = (options.pressedUp != null ? options.pressedUp : function(e) {}); 25 | this.$element = $(element); 26 | if (options.tagData != null) { 27 | this.tagsArray = options.tagData; 28 | } else { 29 | tagData = $('.tag-data', this.$element).html(); 30 | this.tagsArray = (tagData != null ? tagData.split(',') : []); 31 | } 32 | if (this.displayPopovers) { 33 | this.popoverArray = options.popoverData; 34 | } 35 | this.removeTagClicked = function(e) { 36 | if (e.currentTarget.tagName === "A") { 37 | _this.removeTag(e.currentTarget.previousSibling.textContent); 38 | return $(e.currentTarget.parentNode).remove(); 39 | } 40 | }; 41 | this.removeLastTag = function() { 42 | var el; 43 | el = $('.tag', _this.$element).last(); 44 | el.remove(); 45 | return _this.removeTag(_this.tagsArray[_this.tagsArray.length - 1]); 46 | }; 47 | this.removeTag = function(tag) { 48 | if (_this.tagsArray.indexOf(tag) > -1) { 49 | if (_this.displayPopovers) { 50 | _this.popoverArray.splice(_this.tagsArray.indexOf(tag), 1); 51 | } 52 | _this.tagsArray.splice(_this.tagsArray.indexOf(tag), 1); 53 | return _this.renderTags(); 54 | } 55 | }; 56 | this.addTag = function(tag) { 57 | var associatedContent; 58 | if ((_this.restrictTo === false || _this.restrictTo.indexOf(tag) !== -1) && _this.tagsArray.indexOf(tag) < 0 && tag.length > 0 && (_this.exclude === false || _this.exclude.indexOf(tag) === -1) && !_this.excludes(tag)) { 59 | _this.whenAddingTag(tag); 60 | if (_this.displayPopovers) { 61 | associatedContent = _this.definePopover(tag); 62 | _this.popoverArray.push(associatedContent); 63 | } 64 | _this.tagsArray.push(tag); 65 | return _this.renderTags(); 66 | } 67 | }; 68 | this.addTagWithContent = function(tag, content) { 69 | if ((_this.restrictTo === false || _this.restrictTo.indexOf(tag) !== -1) && _this.tagsArray.indexOf(tag) < 0 && tag.length > 0) { 70 | _this.whenAddingTag(tag); 71 | _this.tagsArray.push(tag); 72 | _this.popoverArray.push(content); 73 | return _this.renderTags(); 74 | } 75 | }; 76 | this.renameTag = function(name, newName) { 77 | _this.tagsArray[_this.tagsArray.indexOf(name)] = newName; 78 | return _this.renderTags(); 79 | }; 80 | this.setPopover = function(tag, popoverContent) { 81 | _this.popoverArray[_this.tagsArray.indexOf(tag)] = popoverContent; 82 | return _this.renderTags(); 83 | }; 84 | this.toggleCloseColor = function(e) { 85 | var opacity, tagAnchor; 86 | tagAnchor = $(e.currentTarget); 87 | opacity = tagAnchor.css('opacity'); 88 | opacity = (opacity < 0.8 ? 1.0 : 0.6); 89 | return tagAnchor.css({ 90 | opacity: opacity 91 | }); 92 | }; 93 | this.keyDownHandler = function(e) { 94 | var k, numSuggestions, tag; 95 | k = (e.keyCode != null ? e.keyCode : e.which); 96 | switch (k) { 97 | case 13: 98 | _this.pressedReturn(e); 99 | tag = e.target.value; 100 | if (_this.suggestedIndex !== -1) { 101 | tag = _this.suggestionList[_this.suggestedIndex]; 102 | } 103 | _this.addTag(tag); 104 | e.target.value = ''; 105 | _this.renderTags(); 106 | return _this.hideSuggestions(); 107 | case 46: 108 | case 8: 109 | _this.pressedDelete(e); 110 | if (e.target.value === '') { 111 | _this.removeLastTag(); 112 | } 113 | if (e.target.value.length === 1) { 114 | return _this.hideSuggestions(); 115 | } 116 | break; 117 | case 40: 118 | _this.pressedDown(e); 119 | if (_this.input.val() === '' && (_this.suggestedIndex === -1 || !(_this.suggestedIndex != null))) { 120 | _this.makeSuggestions(e, true); 121 | } 122 | numSuggestions = _this.suggestionList.length; 123 | _this.suggestedIndex = (_this.suggestedIndex < numSuggestions - 1 ? _this.suggestedIndex + 1 : numSuggestions - 1); 124 | _this.selectSuggested(_this.suggestedIndex); 125 | if (_this.suggestedIndex >= 0) { 126 | return _this.scrollSuggested(_this.suggestedIndex); 127 | } 128 | break; 129 | case 38: 130 | _this.pressedUp(e); 131 | _this.suggestedIndex = (_this.suggestedIndex > 0 ? _this.suggestedIndex - 1 : 0); 132 | _this.selectSuggested(_this.suggestedIndex); 133 | if (_this.suggestedIndex >= 0) { 134 | return _this.scrollSuggested(_this.suggestedIndex); 135 | } 136 | break; 137 | case 9: 138 | case 27: 139 | _this.hideSuggestions(); 140 | return _this.suggestedIndex = -1; 141 | } 142 | }; 143 | this.makeSuggestions = function(e, overrideLengthCheck) { 144 | var val; 145 | val = (e.target.value != null ? e.target.value : e.target.textContent); 146 | _this.suggestedIndex = -1; 147 | _this.$suggestionList.html(''); 148 | _this.suggestionList = []; 149 | $.each(_this.suggestions, function(i, suggestion) { 150 | if (_this.tagsArray.indexOf(suggestion) < 0 && suggestion.substring(0, val.length) === val && (val.length > 0 || overrideLengthCheck)) { 151 | _this.$suggestionList.append('
  • ' + suggestion + '
  • '); 152 | return _this.suggestionList.push(suggestion); 153 | } 154 | }); 155 | $('.tags-suggestion', _this.$element).mouseover(_this.selectSuggestedMouseOver); 156 | $('.tags-suggestion', _this.$element).click(_this.suggestedClicked); 157 | if (_this.suggestionList.length > 0) { 158 | return _this.showSuggestions(); 159 | } else { 160 | return _this.hideSuggestions(); 161 | } 162 | }; 163 | this.suggestedClicked = function(e) { 164 | var tag; 165 | tag = e.target.textContent; 166 | if (_this.suggestedIndex !== -1) { 167 | tag = _this.suggestionList[_this.suggestedIndex]; 168 | } 169 | _this.addTag(tag); 170 | _this.input.val(''); 171 | _this.makeSuggestions(e, false); 172 | _this.input.focus(); 173 | return _this.hideSuggestions(); 174 | }; 175 | this.keyUpHandler = function(e) { 176 | var k; 177 | k = (e.keyCode != null ? e.keyCode : e.which); 178 | if (k !== 40 && k !== 38 && k !== 27) { 179 | return _this.makeSuggestions(e, false); 180 | } 181 | }; 182 | this.hideSuggestions = function() { 183 | return $('.tags-suggestion-list', _this.$element).css({ 184 | display: "none" 185 | }); 186 | }; 187 | this.showSuggestions = function() { 188 | return $('.tags-suggestion-list', _this.$element).css({ 189 | display: "block" 190 | }); 191 | }; 192 | this.selectSuggestedMouseOver = function(e) { 193 | $('.tags-suggestion').removeClass('tags-suggestion-highlighted'); 194 | $(e.target).addClass('tags-suggestion-highlighted'); 195 | $(e.target).mouseout(_this.selectSuggestedMousedOut); 196 | return _this.suggestedIndex = $('.tags-suggestion', _this.$element).index($(e.target)); 197 | }; 198 | this.selectSuggestedMousedOut = function(e) { 199 | return $(e.target).removeClass('tags-suggestion-highlighted'); 200 | }; 201 | this.selectSuggested = function(i) { 202 | var tagElement; 203 | $('.tags-suggestion').removeClass('tags-suggestion-highlighted'); 204 | tagElement = $('.tags-suggestion', _this.$element).eq(i); 205 | return tagElement.addClass('tags-suggestion-highlighted'); 206 | }; 207 | this.scrollSuggested = function(i) { 208 | var pos, tagElement, topElement, topPos; 209 | tagElement = $('.tags-suggestion', _this.$element).eq(i); 210 | topElement = $('.tags-suggestion', _this.$element).eq(0); 211 | pos = tagElement.position(); 212 | topPos = topElement.position(); 213 | return $('.tags-suggestion-list', _this.$element).scrollTop(pos.top - topPos.top); 214 | }; 215 | this.adjustInputPosition = function() { 216 | var pBottom, pLeft, pTop, tagElement, tagPosition; 217 | tagElement = $('.tag', _this.$element).last(); 218 | tagPosition = tagElement.position(); 219 | pLeft = tagPosition != null ? tagPosition.left + tagElement.outerWidth(true) : 0; 220 | pTop = tagPosition != null ? tagPosition.top : 0; 221 | $('.tags-input', _this.$element).css({ 222 | paddingLeft: pLeft, 223 | paddingTop: pTop 224 | }); 225 | pBottom = tagPosition != null ? tagPosition.top + tagElement.outerHeight(true) : 22; 226 | return _this.$element.css({ 227 | height: pBottom 228 | }); 229 | }; 230 | this.renderTags = function() { 231 | var tagList; 232 | tagList = $('.tags', _this.$element); 233 | tagList.html(''); 234 | _this.input.attr('placeholder', (_this.tagsArray.length === 0 ? _this.promptText : '')); 235 | $.each(_this.tagsArray, function(i, tag) { 236 | tag = $(_this.formatTag(i, tag)); 237 | $('a', tag).click(_this.removeTagClicked); 238 | $('a', tag).mouseover(_this.toggleCloseColor); 239 | $('a', tag).mouseout(_this.toggleCloseColor); 240 | if (_this.displayPopovers) { 241 | $('span', tag).mouseover(function() { 242 | return tag.popover('show'); 243 | }); 244 | $('span', tag).mouseout(function() { 245 | return tag.popover('hide'); 246 | }); 247 | } 248 | return tagList.append(tag); 249 | }); 250 | return _this.adjustInputPosition(); 251 | }; 252 | this.formatTag = function(i, tag) { 253 | var popoverContent; 254 | if (_this.displayPopovers === true) { 255 | popoverContent = _this.popoverArray[_this.tagsArray.indexOf(tag)]; 256 | return "
    " + tag + "
    "; 257 | } else { 258 | return "
    " + tag + "
    "; 259 | } 260 | }; 261 | this.addDocumentListeners = function() { 262 | return $(document).mouseup(function(e) { 263 | var container; 264 | container = $('.tags-suggestion-list', _this.$element); 265 | if (container.has(e.target).length === 0) { 266 | return _this.hideSuggestions(); 267 | } 268 | }); 269 | }; 270 | this.init = function() { 271 | this.input = $(""); 272 | this.input.keydown(this.keyDownHandler); 273 | this.input.keyup(this.keyUpHandler); 274 | this.$element.append(this.input); 275 | this.$suggestionList = $(''); 276 | this.$element.append(this.$suggestionList); 277 | this.renderTags(); 278 | return this.addDocumentListeners(); 279 | }; 280 | this.init(); 281 | return this; 282 | }; 283 | return $.fn.tags = function(options) { 284 | return this.each(function() { 285 | var tags; 286 | return tags = new $.tags(this, options); 287 | }); 288 | }; 289 | }); 290 | 291 | }).call(this); 292 | --------------------------------------------------------------------------------