├── 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 | "