Skip to content
Browse files

general progress on cleanup and jQ UI compliance

add some more examples
allow specially instantiating the widget on INPUT, UL, or OL to prefill tag data from what exists in the markup, and set singleField in the case of INPUT
make events jQuery UI-compliant
fix and tweak theme and base CSS
clean up JS
clean up docs
  • Loading branch information...
1 parent 1c90239 commit 07a7a731546787bdbf9743c3b55f238166229af1 @aehlke committed Jun 13, 2011
Showing with 647 additions and 293 deletions.
  1. +98 −79 README.markdown
  2. +9 −0 TODO
  3. +36 −0 css/examples.css
  4. +19 −39 css/jquery.tagit.css
  5. +178 −39 css/master.css
  6. +13 −6 css/tagit.ui-zendesk.css
  7. +195 −59 example.html
  8. +99 −71 js/tag-it.js
  9. BIN screenshot.png
View
177 README.markdown
@@ -1,59 +1,69 @@
-# tag-it: a jQuery plugin
+# Tag-it: a jQuery UI plugin
+
+Tag-it was originally inspired by the "tag suggestion" form field in ZenDesk.com. Now it is a full jQuery UI widget, supporting various configurations and themes.
-> After looking for a jQuery plugin for handling a 'tag suggestion' form field, in much the same way ZenDesk.com does, I ended up developing a customization of jQuery UI that does the same interaction.
-[Levy Carneiro Jr.](http://github.com/levycarneiro)
## Demo
-![Screenshot](http://github.com/grobie/tag-it/raw/master/screenshot.png)
+![Screenshot](http://aehlke.github.com/tag-it/screenshot.png)
+
+Check the [example.html](http://aehlke.github.com/tag-it/example.html) for several demos.
-Check the [example.html](http://github.com/grobie/tag-it/blob/master/example.html) for a demo.
## Usage
-First, load [jQuery](http://jquery.com/), [jQuery UI](http://jqueryui.com/) and the plugin:
+First, load [jQuery](http://jquery.com/) (1.5.x or greater), [jQuery UI](http://jqueryui.com/) (1.8.x or greater), and the plugin:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/tag-it.js" type="text/javascript" charset="utf-8"></script>
-Notice, to make it work under IE you have to additionally load 'js/ie-compat.js'.
+The plugin requires a jQuery UI theme to be present, as well as its own included base CSS file ([jquery.tagit.css](http://github.com/aehlke/tag-it/raw/master/css/jquery.tagit.css). Here we use the Flick theme as an example:
- <script src="js/ie-compat.js" type="text/javascript" charset="utf-8"></script>
+ <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">
+ <link href="css/jquery.tagit.css" rel="stylesheet" type="text/css">
Now, let's attach it to an existing `<ul>` box:
<script type="text/javascript">
- $(document).ready(function() {
- $("#mytags").tagit();
- });
+ $(document).ready(function() {
+ $("#mytags").tagit();
+ });
</script>
<ul id="mytags">
- <!-- Existing list items will be pre-added to the tags -->
- <li>Tag1</li>
- <li>Tag2</li>
+ <!-- Existing list items will be pre-added to the tags -->
+ <li>Tag1</li>
+ <li>Tag2</li>
</ul>
This will turn the list into a tag-it list:
- <ul id="mytags" class="tagit">
- <!-- Existing list items will be pre-added to the tags -->
- <li class="tagit-choice">
- Tag1
- <a class="close">x</a>
- <input type="hidden" style="display:none;" value="Tag1" name="item[tags][]">
- </li>
- <li class="tagit-choice">
- Tag2
- <a class="close">x</a>
- <input type="hidden" style="display:none;" value="Tag2" name="item[tags][]">
- </li>
- <li class="tagit-new">
- <input class="tagit-input ui-autocomplete-input" type="text" autocomplete="off" role="textbox" aria-autocomplete="list" aria-haspopup="true">
- </li>
+ <ul id="mytags" class="tagit ui-widget ui-widget-content ui-corner-all">
+ <!-- Existing list items will be pre-added to the tags. -->
+ <li class="tagit-choice ui-widget-content ui-state-default ui-corner-all">
+ <span class="tagit-label">Tag1</span>
+ <a class="close"><span class="ui-icon ui-icon-close"></span></a>
+ <input type="hidden" style="display:none;" value="Tag1" name="item[tags][]">
+ </li>
+ <li class="tagit-choice ui-widget-content ui-state-default ui-corner-all">
+ <span class="tagit-label">Tag2</span>
+ <a class="close"><span class="ui-icon ui-icon-close"></span></a>
+ <input type="hidden" style="display:none;" value="Tag2" name="item[tags][]">
+ </li>
+ <li class="tagit-new">
+ <input type="text" class="ui-widget-content ui-autocomplete-input" autocomplete="off" role="textbox" aria-autocomplete="list" aria-haspopup="true">
+ </li>
</ul>
+There are several other possible configurations and ways to instantiate the widget, including one that uses a single comma-delimited `input` field rather than one per tag, so see the **Options** documentation below as well as the [examples page](http://aehlke.github.com/tag-it/example.html) which demonstrates most of them.
+
+
+## Theming
+
+Tag-it is as easily themeable as any jQuery UI widget, using your own theme made with [Themeroller](http://jqueryui.com/themeroller/), or one of the jQuery UI [premade themes](http://jqueryui.com/themeroller/#themeGallery). The old ZenDesk-like theme is included as an optional CSS file ([tagit.ui-zendesk.css](http://github.com/aehlke/tag-it/raw/master/css/tagit.ui-zendesk.css)).
+
+
## Options
Tag-it accepts several options to customize the behaviour:
@@ -63,7 +73,7 @@ Tag-it accepts several options to customize the behaviour:
Used to build the name of the hidden input field: `itemName[fieldName][]`.
$("#mytags").tagit({
- itemName: "user"
+ itemName: "user"
});
Defaults to *item*.
@@ -73,7 +83,7 @@ Defaults to *item*.
Used to build the name of the hidden input field: `itemName[fieldName][]`.
$("#mytags").tagit({
- fieldName: "skills"
+ fieldName: "skills"
});
Defaults to *tags*.
@@ -83,44 +93,11 @@ Defaults to *tags*.
Used as source for the autocompletion.
$("#mytags").tagit({
- availableTags: ["c++", "java", "php", "javascript", "ruby", "python", "c"]
+ availableTags: ["c++", "java", "php", "javascript", "ruby", "python", "c"]
});
Defaults to an empty array *[]*.
-### onTagAdded (function, Callback)
-
-Can be used to add custom behaviour before the Tag is added to the DOM.
-The function receives the tag as parameter.
-
- $("#mytags").tagit({
- onTagAdded: function(tag) {
- // do something special
- }
- });
-
-### onTagRemoved (function, Callback)
-
-Can be used to add custom behaviour before the Tag is removed from the DOM.
-The function receives the tag as parameter.
-
- $("#mytags").tagit({
- onTagRemoved: function(tag) {
- // do something special
- }
- });
-
-### onTagClicked (function, Callback)
-
-Can be used to add custom behaviour when the Tag is clicked from the DOM.
-The function receives the tag as parameter.
-
- $("#mytags").tagit({
- onTagClicked: function(tag) {
- // do something special
- }
- });
-
### tagSource (function)
Can be overwritten in order to use custom autocompletion sources like Ajax requests.
@@ -142,7 +119,7 @@ Defaults to *true*.
### allowSpaces (boolean)
When allowSpaces is enabled the user is not required to wrap multi-word tags in quotation marks.
-ie. user can enter John Smith instead of "John Smith"
+For example, the user can enter `John Smith` instead of `"John Smith"`.
Defaults to *false*.
@@ -151,48 +128,90 @@ Defaults to *false*.
When enabled, will use a single hidden field for the form, rather than one per tag.
It will delimit tags in the field with **singleFieldDelimiter**.
-Defaults to *false*
+Defaults to *false*, unless Tag-it was created on an `input` element, in which case **singleField** will be overridden as true.
### singleFieldDelimiter (String)
-Defaults to *','*
+Defaults to *","*
### singleFieldNode (DomNode)
+
Set this to an input DOM node to use an existing form field.
Any text in it will be erased on init. But it will be populated with the text of tags as they are created, delimited by **singleFieldDelimiter**.
If this is not set, we create an input node for it, with the name given in **fieldName**, ignoring **itemName**.
-Defalts to *null*
+Defaults to *null*, unless Tag-it was created on an `input` element, in which case **singleFieldNode** will be overridden with that element.
### tabIndex (integer)
-Optionally set a *tabindex* attribute on the input that gets created for tag-it.
+Optionally set a *tabindex* attribute on the `input` that gets created for tag-it user input.
Defaults to *null*
+## Events
+
+### onTagAdded (function, Callback)
+
+Can be used to add custom behaviour before the Tag is added to the DOM.
+The function receives an empty event, and the tag as parameters.
+
+ $("#mytags").tagit({
+ onTagAdded: function(event, tag) {
+ // do something special
+ }
+ });
+
+### onTagRemoved (function, Callback)
+
+Can be used to add custom behaviour before the Tag is removed from the DOM.
+The function receives an empty event, and the tag as parameters.
+
+ $("#mytags").tagit({
+ onTagRemoved: function(event, tag) {
+ // do something special
+ }
+ });
+
+### onTagClicked (function, Callback)
+
+Can be used to add custom behaviour when the Tag is clicked from the DOM.
+The function receives the click event and the tag as parameters.
+
+ $("#mytags").tagit({
+ onTagClicked: function(event, tag) {
+ // do something special
+ }
+ });
+
+
## Methods
-### removeAll()
-Clears the widget of all tags -- removes each tag it contains, so the onTagRemoved event callback (if set in the options) will be called for each.
+### assignedTags()
+Returns an array of the text values of all the tags currently in the widget.
+
+ $("#mytags").tagit("assignedTags");
- $("#mytags").tagit('removeAll');
+### createTag(tagName, additionalClass)
+Adds new tag to the list. The `additionalClass` parameter is an optional way to add classes to the tag element.
-### createTag(tagName)
-Adds new tag to the list.
+ $("#mytags").tagit("createTag", "brand-new-tag");
+
+### removeAll()
+Clears the widget of all tags -- removes each tag it contains, so the onTagRemoved event callback (if set in the options) will be called for each.
- $("#mytags").tagit('createTag', 'brandNewTag');
+ $("#mytags").tagit("removeAll");
## Authors
-* [Levy Carneiro Jr.](http://github.com/levycarneiro)
+* [Levy Carneiro Jr.](http://github.com/levycarneiro) *original author*
* [Martin Rehfeld](http://github.com/martinrehfeld)
* [Tobias Schmidt](http://github.com/grobie)
* [Skylar Challand](http://github.com/sskylar)
-* [Alex Ehlke](http://github.com/aehlke)
+* [Alex Ehlke](http://github.com/aehlke) *current maintainer*
## License
-tag-it is released under the MIT license.
+tag-it is released under the [MIT license](http://github.com/aehlke/tag-it/raw/master/LICENSE).
View
9 TODO
@@ -3,3 +3,12 @@ current one is deprecated, though still functional.
* Add keyboard shortcuts for highlighting tags to remove them upon backspace.
+* minLength for autocomplete
+
+* hide the singleFieldNode
+
+* add quotesAllowed option (or quotedSpacesAllowed)
+
+* autogrow the input (http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields)
+
+
View
36 css/examples.css
@@ -0,0 +1,36 @@
+@charset "UTF-8";
+
+
+#nav {
+ left:0;
+}
+#header {
+ padding-top:3em;
+}
+
+#header h1, #header h2 {
+ margin: 0 0 .2em 0;
+ padding-top: 1.5em;
+}
+hr + h3, hr + h4 {
+ margin-top: 0;
+}
+hr {
+ margin-bottom: .4em;
+}
+
+
+
+.myform {
+ padding:20px 0px;
+}
+.myform div.line {
+ clear:both;
+ min-height:50px;
+ margin-bottom:15px;
+}
+.myform label {
+ display:block;
+ font-weight:bold;
+ margin-bottom:5px;
+}
View
58 css/jquery.tagit.css
@@ -1,27 +1,8 @@
-/*.ui-autocomplete {
- background-color: #eee;
- position: absolute;
- cursor: default;
-}
-.ui-autocomplete .ui-menu-item {
- list-style: none;
-}
-.ui-autocomplete .ui-menu-item a {
- display: block;
- padding: 4px 6px;
- text-decoration: none;
- line-height: 12px;
-}
-.ui-autocomplete .ui-menu-item a.ui-state-hover,
-.ui-autocomplete .ui-menu-item a.ui-state-active {
- background-color: #78959D;
- color: #fff;
- margin: 0;
-}
-*/
ul.tagit {
padding: 1px 5px;
overflow: auto;
+ margin-left: inherit; /* usually we don't want the regular ul margins. */
+ margin-right: inherit;
}
ul.tagit li {
display: block;
@@ -31,21 +12,30 @@ ul.tagit li {
ul.tagit li.tagit-choice {
padding: .2em 18px .2em .5em;
position: relative;
+ line-height: inherit;
}
ul.tagit li.tagit-new {
- padding: 2px 4px 1px 0;
+ padding: .25em 4px .25em 0;
}
-ul.tagit li.tagit-choice input {
- display: block;
- float: left;
- margin: 2px 5px 2px 0;
-}
-ul.tagit li.tagit-choice a.tagLabel {
+
+ul.tagit li.tagit-choice a.tagit-label {
cursor: pointer;
text-decoration: none;
}
-ul.tagit li.tagit-choice a.close {
+ul.tagit li.tagit-choice .close {
cursor: pointer;
+ /*display: inline-block;*/
+ position: absolute;
+ right: .1em;
+ top: 50%;
+ margin-top: -8px;
+}
+
+
+ul.tagit li.tagit-choice input {
+ display: block;
+ float: left;
+ margin: 2px 5px 2px 0;
}
ul.tagit input[type="text"] {
-moz-box-sizing: border-box;
@@ -56,16 +46,6 @@ ul.tagit input[type="text"] {
margin: 0;
padding: 0;
width: inherit;
- border-color: #C6C6C6;
background-color: inherit;
- color: #333333;
outline: none;
}
-ul.tagit .tagit-choice a.ui-icon {
- display: inline-block;
- position: absolute;
- top: 50%;
- margin-top: -8px;
- right: .1em;
-}
-
View
217 css/master.css
@@ -1,39 +1,178 @@
-@charset "UTF-8";
-
-/* base */
-html {
- font-size: 62.5%;
-}
-a {
- text-decoration:underline;
-}
-p {
- margin-bottom:10px;
- line-height:18px;
-}
-/* end of base */
-
-body, input, a {
- font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
- color: #333;
- font-size:12px;
-}
-
-#content {
- width:600px;
- margin:20px 0 20px 40px;
-}
-
-.myform {
- padding:20px 0px;
-}
-.myform div.line {
- clear:both;
- min-height:50px;
- margin-bottom:15px;
-}
-.myform label {
- display:block;
- font-weight:bold;
- margin-bottom:5px;
-}
+@charset "UTF-8";
+
+html, body {
+ color:#333;
+ background:#002F2F/*#232f2e*/;
+ line-height:1.3;
+ margin:0;
+ padding:0;
+ font-family: 'Lucida Grande', arial, sans-serif;
+}
+a {
+ color:#636363;
+ /*color:#1155bd;*/
+}
+a:hover {
+ text-decoration:none;
+}
+hr {
+ border: none;
+ background-color: #ccc;
+ height: 6px;
+ margin: 1em 0;
+}
+em {
+ font-style:italic;
+}
+h1,h2,h3 {
+ /*color:#4f6f6c;*/
+ color:#046380;
+ font-family: 'Brawler', arial, sans-serif;
+ font-weight: normal;
+}
+h1,h2,h3,h4 {
+ margin:1.5em 0 .5em 0;
+}
+h1 {
+ font-size:2.6em;
+}
+h2 {
+ font-size:1.8em;
+}
+h3 {
+ font-size:1.5em;
+}
+h4 {
+ font-size:129%;
+}
+p {
+ margin: 1.5em 0 1em 0;
+}
+pre {
+ background:#eee;
+ border:1px solid #ccc;
+ font-size:100%;
+ overflow:auto;
+ margin:0 0 20px;
+ padding:20px;
+}
+code {
+ font-size:100%;
+ margin:0;
+ padding:0;
+}
+ul {
+ list-style:disc;
+}
+div#wrapper {
+ background:#fff;
+ width:560px;
+ border:10px solid #0f1616;
+ border-width:0 10px 10px;
+ margin:0 auto;
+ padding:15px 20px;
+}
+div#header {
+ position:relative;
+ border-bottom:1px dotted;
+ margin:0 0 10px;
+ padding:0 0 4px;
+}
+ul#nav {
+ position:absolute;
+ top:.6em;
+ right:0;
+ list-style:none;
+ margin:0;
+ padding:0;
+}
+ul#nav li {
+ display:inline;
+ padding:0 0 0 2px;
+ font-size: 1.2em;
+ font-weight: bold;
+}
+ul#nav a, .highlighted {
+ background-color:#FFFBD0;
+ padding: .3em .4em;
+}
+ul#nav a {
+ padding: .4em .5em;
+}
+#footer {
+ border-top:1px dotted;
+ margin:40px 0 0;
+ padding:10px 0 0;
+ font-size: .8em;
+}
+.left {
+ float:left;
+}
+.right {
+ float:right;
+}
+.clear {
+ clear:both;
+}
+.multiselect {
+ width:460px;
+ height:200px;
+}
+#switcher {
+ margin-top:20px;
+}
+strong,h1,h4,h5,h6 {
+ font-weight:bold;
+}
+#header p,#header h1,form {
+ margin:0;
+ padding:0;
+}
+#header h1 {
+ margin-bottom: .2em;
+}
+.weak, .weak a, .weak a:visited {
+ color: gray;
+}
+
+hr + h2, hr + h3 {
+ margin-top: .5em;
+}
+form + hr, p + hr {
+ margin-top: 2em;
+}
+
+label {
+ float: left;
+ width: 38px;
+ margin-right: 1em;
+ font-size: 1.2em;
+ line-height: 2em;
+}
+ul.tagit {
+ width: 495px;
+}
+
+#tag-icon {
+ float: left;
+ margin-right: 1.4em;
+ position: relative;
+ top: .1em;
+}
+#title-jquery, #title-tag-it {
+ display: block;
+}
+#title-tag-it {
+ font-size: 1.2em;
+}
+#title-jquery {
+ font-size: .75em;
+ font-weight: normal;
+ position: relative;
+ left: .2em;
+ top: .1em;
+}
+#feature-list {
+ margin-top: 2.5em;
+}
+
View
19 css/tagit.ui-zendesk.css
@@ -6,6 +6,7 @@ ul.tagit {
border-style: solid;
border-width: 1px;
border-color: #C6C6C6;
+ background: inherit;
}
ul.tagit li.tagit-choice {
-moz-border-radius: 6px;
@@ -19,21 +20,23 @@ ul.tagit li.tagit-choice {
color: #555;
font-weight: normal;
}
-ul.tagit .tagit-choice a.ui-icon {
- right: .4em;
-}
ul.tagit li.tagit-choice a.close {
text-decoration: none;
}
-ul.tagit li.tagit-choice a.ui-icon {
+ul.tagit li.tagit-choice .close {
+ right: .4em;
+}
+ul.tagit li.tagit-choice .ui-icon {
background-image: none;
text-indent: inherit;
width: inherit;
height: inherit;
}
-ul.tagit li.tagit-choice a.close:before {
+ul.tagit li.tagit-choice .close:before {
content: '×';
- font-weight: bold;
+ font-family: arial, sans-serif;
+ font-size: 16px;
+ line-height: 16px;
color: #777;
}
ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove {
@@ -44,5 +47,9 @@ ul.tagit li.tagit-choice a.tagLabel:hover,
ul.tagit li.tagit-choice a.close:hover:before {
color: #222;
}
+ul.tagit input[type="text"] {
+ color: #333333;
+ background: none;
+}
View
254 example.html
@@ -2,99 +2,235 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Tag it! example</title>
+ <title>Tag-it! example</title>
- <!--<link href="css/reset.css" rel="stylesheet" type="text/css">-->
- <link href="css/master.css" rel="stylesheet" type="text/css">
+ <!-- These few CSS files are just to make this example page look nice. You can ignore them. -->
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.9.0/build/reset-fonts/reset-fonts.css">
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.9.0/build/base/base-min.css">
+ <link href='http://fonts.googleapis.com/css?family=Brawler' rel='stylesheet' type='text/css'>
+ <link href="css/master.css" rel="stylesheet" type="text/css">
+ <link href="css/examples.css" rel="stylesheet" type="text/css">
+ <!-- /ignore -->
+
+ <!-- These 2 CSS files are required: any 1 jQuery UI theme CSS, plus the Tag-it base CSS. -->
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">
- <link href="css/jquery.tagit.css" rel="stylesheet" type="text/css">
- <!--<link href="css/tagit.ui-zendesk.css" rel="stylesheet" type="text/css">-->
+ <link rel="stylesheet" type="text/css" href="css/jquery.tagit.css">
+
+ <!-- This is an optional CSS theme that only applies to this widget. Use it in addition to the jQuery UI theme. -->
+ <link href="css/tagit.ui-zendesk.css" rel="stylesheet" type="text/css">
- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
+ <!-- jQuery and jQuery UI are required dependencies. -->
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js" type="text/javascript" charset="utf-8"></script>
+ <!-- The real deal -->
<script src="js/tag-it.js" type="text/javascript" charset="utf-8"></script>
<script>
$(function(){
var sampleTags = ['c++', 'java', 'php', 'coldfusion', 'javascript', 'asp', 'ruby', 'python', 'c', 'scala', 'groovy', 'haskell', 'perl', 'erlang', 'apl', 'cobol', 'go', 'lua'];
- $("#mytags").tagit({
+
+ //-------------------------------
+ // Minimal
+ //-------------------------------
+ $('#myTags').tagit();
+
+ //-------------------------------
+ // Single field
+ //-------------------------------
+ $('#singleFieldTags').tagit({
availableTags: sampleTags,
+ // This will make Tag-it submit a single form value, as a comma-delimited field.
+ singleField: true,
+ singleFieldNode: $('#mySingleField')
+ });
+
+ // singleFieldTags2 is an INPUT element, rather than a UL as in the other
+ // examples, so it automatically defaults to singleField.
+ $('#singleFieldTags2').tagit({
+ availableTags: sampleTags
+ });
+
+
+ //-------------------------------
+ // Preloading data in markup
+ //-------------------------------
+ $('#myULTags').tagit({
+ availableTags: sampleTags, // this param is of course optional. it's for autocomplete.
// configure the name of the input field (will be submitted with form), default: item[tags]
itemName: 'item',
fieldName: 'tags'
});
- $("#myTagsSingleField").tagit({
+ //-------------------------------
+ // Tag events
+ //-------------------------------
+ $('#eventTags').tagit({
+ availableTags: sampleTags,
+ onTagRemoved: function(evt, tag) {
+ console.log(evt);
+ alert('This tag is being removed: ' + tag.text());
+ },
+ onTagClicked: function(evt, tag) {
+ console.log(tag);
+ alert('This tag was clicked: ' + tag.text());
+ }
+ }).tagit('option', 'onTagAdded', function(evt, tag) {
+ // Add this callbackafter we initialize the widget,
+ // so that onTagAdded doesn't get called on page load.
+ alert('This tag is being added: ' + tag.text());
+ });
+
+ //-------------------------------
+ // Tag-it methods
+ //-------------------------------
+ $('#methodTags').tagit({
availableTags: sampleTags,
- // configure the name of the input field (will be submitted with form), default: item[tags]
- singleField: true,
- singleFieldNode: $('#mySingleField')
});
- $("#eventTags").tagit({
+ //-------------------------------
+ // Allow spaces without quotes.
+ //-------------------------------
+ $('#allowSpacesTags').tagit({
availableTags: sampleTags,
- singleField: true
+ allowSpaces: true
});
-
- $('#switcher').themeswitcher();
+ //-------------------------------
+ // Remove confirmation
+ //-------------------------------
+ $('#removeConfirmationTags').tagit({
+ availableTags: sampleTags,
+ removeConfirmation: true
+ });
+
});
</script>
</head>
<body>
- <p><strong>Tag it!</strong> ~ Tag editor and suggestions</p>
- <p>After looking for a jQuery plugin for handling a 'tag suggestion' form field, in much the same way ZenDesk.com does, I ended up developing a customization of jQuery UI that does the same interaction.</p>
+<a href="http://github.com/aehlke/tag-it"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png" alt="Fork me on GitHub" /></a>
- <form class="myform">
- <label>Tags</label>
- <ul id="mytags">
- <!-- Existing list items will be pre-added to the tags -->
- <li>Tag1</li>
- <li>Tag2</li>
+<div id="wrapper">
+ <div id="header">
+ <h2>Tag-it! Usage Examples</h2>
+
+ <ul id="nav">
+ <li><a href="http://github.com/aehlke/tag-it">&laquo; back to widget home</a></li>
</ul>
- <input type="submit" value="Submit">
- </form>
-
- <hr>
-
- <form class="myform">
- <p>Example using a single input form field to hold all the tag values, instead of one per tag (see settings.singleField):</p>
- <p>
- Normally this input field will be hidden -- but we leave it visible here so you can see how it is manipulated by the widget:<br>
- <input name="tags" id="mySingleField" value="Tag1, Tag2">
- </p>
- <label>Tags</label>
- <ul id="myTagsSingleField"></ul>
- <a href="#" onclick="$('#myTagsSingleField').tagit('removeAll');return false;">Clear tags</a>
- <div class="line">
+ </div>
+
+ <div id="content">
+ <p>These demo various features of Tag-it. View the source to see how each works.</p>
+
+ <hr>
+ <h3>Minimal</h3>
+ <form>
+ <p>
+ Vanilla example &mdash; the absolute minimum amount of code required, no configuration. No autocomplete, either. See the other examples for that.
+ </p>
+ <ul id="myTags"></ul>
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Single Input Field</h3>
+ <form>
+ <p>
+ Example using a single input form field to hold all the tag values, instead of one per tag (see settings.singleField).
+ This method is particularly useful if you have a form with one input field for comma-delimited tags that you want to trivially "upgrade" to this fancy jQuery UI widget.
+ This configuration will also degrade nicely as well for browsers without JS &mdash; the default behavior is to have one input per tag, which does not degrade as well as one comma-delimited input.
+ </p>
+ <p>
+ Normally this input field will be hidden &mdash; we leave it visible here so you can see how it is manipulated by the widget:
+ <input name="tags" id="mySingleField" value="Apple, Orange" disabled="true"> <!-- only disabled for demonstration purposes -->
+ </p>
+ <ul id="singleFieldTags"></ul>
<input type="submit" value="Submit">
- </div>
- </form>
-
- <form class="myform">
- <div class="line">
- <p>Example of tag events. Try removing or adding a tag.</p>
- </div>
- <div class="line">
- <label>Tags</label>
- <ul id="eventTags"></ul>
- </div>
- <div class="line">
- <a href="#" onclick="$('#eventTags').tagit('removeAll');">Clear tags</a>
- </div>
- </form>
-</div>
+ </form>
+
+ <hr>
+ <h3><a name="graceful-degredation"></a>Single Input Field (2)</h3>
+ <form>
+ <p>
+ If you instantiate Tag-it on an INPUT element, it will default to being singleField, with that INPUT element as the singleFieldNode. This is the simplest way to have a gracefully-degrading tag widget.
+ </p>
+ <input name="tags" id="singleFieldTags2" value="Apple, Orange">
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Spaces Allowed Without Quotes</h3>
+ <p>You can already do multiword tags with spaces in them by default, but those must be wrapped in quotes. This option lets you use spaces without requiring the user to quote the input.</p>
+ <p>There are normally 5 ways to insert a tag after inputting some text: space, comma, enter, selecting an autocomplete option, or defocusing the widget. With the "allowSpaces" option set to true, space no longer inserts a tag, it just adds a space to the current tag input.</p>
+ <form>
+ <p></p>
+ <ul id="allowSpacesTags"></ul>
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Preloading Data in Markup</h3>
+ <form>
+ <p>
+ Using a UL in HTML to prefill the widget with some tags.
+ </p>
+ <ul id="myULTags">
+ <!-- Existing list items will be pre-added to the tags. -->
+ <li>Tag1</li>
+ <li>Tag2</li>
+ </ul>
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Events</h3>
+ <form>
+ <p>Example of tag events. Try adding or removing a tag, or clicking on a tag's label.</p>
+ <ul id="eventTags">
+ <li>Click my label</li>
+ <li>Remove me</li>
+ </ul>
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Methods</h3>
+ <form>
+ <p>Demos the available widget methods. Click the links below the widget to try them.</p>
+ <ul id="methodTags"></ul>
+ <p><a href="#" onclick="var inp=prompt('Enter a tag value to test the createTag method.');$('#methodTags').tagit('createTag', inp);return false;">Create tag</a></p>
+ <p><a href="#" onclick="$('#methodTags').tagit('removeAll');return false;">Clear tags</a></p>
+ <input type="submit" value="Submit">
+ </form>
+
+ <hr>
+ <h3>Remove Confirmation</h3>
+ <form>
+ <p>
+ When removeConfirmation is enabled the user has to press the backspace key twice to remove the last tag.
+ </p>
+ <ul id="removeConfirmationTags">
+ <li>backspace me</li>
+ <li>me too</li>
+ </ul>
+ <input type="submit" value="Submit">
+ </form>
+ </div>
- <script type="text/javascript" src="http://jqueryui.com/themeroller/themeswitchertool/"></script>
- <div id="switcher"></div>
- <p>Built with <a href="http://jquery.com/" target="_blank">jQuery</a> and <a href="http://jqueryui.com/" target="_blank">jQuery UI</a>.</p>
- <p>Originally created by <a href="http://levycarneiro.com/">Levy Carneiro Jr</a>. Currently maintained by <a href="http://github.com/aehlke">Alex Ehlke</a>.</p>
+
+ <div id="footer">
+ <div class="left">
+ <p>Built with <a href="http://jquery.com/" target="_blank">jQuery</a> and <a href="http://jqueryui.com/" target="_blank">jQuery UI</a>.</p>
+ <p>Originally created by <a href="http://levycarneiro.com/">Levy Carneiro Jr</a>. Currently maintained by <a href="http://github.com/aehlke">Alex Ehlke</a>.</p>
+ </div>
+ <p class="right weak">Template adopted from <a href="http://orderedlist.com/demos/fancy-zoom-jquery/">orderedlist.com</a></p>
+ <br class="clear"/>
+ </div>
+</div>
</body>
</html>
View
170 js/tag-it.js
@@ -3,107 +3,120 @@
$.widget('ui.tagit', {
options: {
- 'itemName' : 'item',
- 'fieldName' : 'tags',
- 'availableTags' : [],
- // callback: called when a tag is added
- 'onTagAdded' : null,
- // callback: called when a tag is removed
- 'onTagRemoved' : null,
- // callback: called when a tag is clicked
- 'onTagClicked' : null,
- 'tagSource' : null,
- 'removeConfirmation': false,
- 'caseSensitive': true,
- 'allowSpaces': false, // when enabled, quotes are not neccesary for inputting multi-word tags
+ itemName : 'item',
+ fieldName : 'tags',
+ availableTags : [],
+ tagSource : null,
+ removeConfirmation: false,
+ caseSensitive : true,
+ allowSpaces : false, // when enabled, quotes are not neccesary for inputting multi-word tags
// The below options are for using a single field instead of several for our form values.
- 'singleField': false, // When enabled, will use a single hidden field for the form, rather than one per tag.
- // It will delimit tags in the field with singleFieldDelimiter.
- 'singleFieldDelimiter': ',',
- 'singleFieldNode': null, // Set this to an input DOM node to use an existing form field.
+ singleField: false, // When enabled, will use a single hidden field for the form, rather than
+ // one per tag. It will delimit tags in the field with singleFieldDelimiter.
+ singleFieldDelimiter: ',',
+ singleFieldNode: null, // Set this to an input DOM node to use an existing form field.
// Any text in it will be erased on init. But it will be populated with
// the text of tags as they are created, delimited by singleFieldDelimiter.
// If this is not set, we create an input node for it, with the name
// given in settings.fieldName, ignoring settings.itemName.
- 'tabIndex': null // Optionally set a tabindex attribute on the input that gets created for tag-it.
+ tabIndex: null, // Optionally set a tabindex attribute on the input that gets created for tag-it.
+
+
+ // Event callbacks.
+ onTagAdded : null,
+ onTagRemoved: null,
+ onTagClicked: null
},
_create: function() {
// for handling static scoping inside callbacks
- var self = this;
+ var that = this;
+
+ // There are 2 kinds of DOM nodes this widget can be instantiated on:
+ // 1. UL, OL, or some element containing either of these.
+ // 2. INPUT, in which case 'singleField' is overridden to true, a UL is created
+ // and the INPUT is hidden.
+ if (this.element.is('input')) {
+ this.tagList = $('<ul></ul>').insertAfter(this.element);
+ this.options.singleField = true;
+ this.options.singleFieldNode = this.element;
+ this.element.css('display', 'none');
+ } else {
+ this.tagList = this.element.find('ul, ol').andSelf().last();
+ }
- this.tagList = this.element;
- this._tagInput = $('<input class="tagit-input" type="text" ' + (this.options.tabIndex ? 'tabindex="' + this.options.tabIndex + '"' : '') + '>');
+ this._tagInput = $('<input type="text">').addClass('ui-widget-content');
+ if (this.options.tabIndex) {
+ this._tagInput.attr('tabindex', this.options.tabIndex);
+ }
this.options.tagSource = this.options.tagSource || function(search, showChoices) {
var filter = search.term.toLowerCase();
- var choices = $.grep(self.options.availableTags, function(element) {
+ var choices = $.grep(that.options.availableTags, function(element) {
// Only match autocomplete options that begin with the search term.
// (Case insensitive.)
return (element.toLowerCase().indexOf(filter) === 0);
});
- showChoices(self._subtractArray(choices, self.assignedTags()));
+ showChoices(that._subtractArray(choices, that.assignedTags()));
};
this.tagList
.addClass('tagit')
.addClass('ui-widget ui-widget-content ui-corner-all')
// create the input field.
- .append($('<li class="tagit-new"></li>\n').append(this._tagInput))
+ .append($('<li class="tagit-new"></li>').append(this._tagInput))
.click(function(e) {
var target = $(e.target);
- if (target.hasClass('close')) {
- // Removes a tag when the little 'x' is clicked.
- // Event is binded to the UL, otherwise a new tag (LI > A) wouldn't have this event attached to it.
- self.removeTag(target.parent());
- } else if (target.hasClass('tagit-label') && self.options.onTagClicked) {
- self.options.onTagClicked(target.parent());
+ if (target.hasClass('tagit-label')) {
+ that._trigger('onTagClicked', e, target.closest('.tagit-choice'));
} else {
// Sets the focus() to the input field, if the user clicks anywhere inside the UL.
// This is needed because the input field needs to be of a small size.
- self._tagInput.focus();
+ that._tagInput.focus();
}
});
- // Add existing tags.
+ // Add existing tags from the list, if any.
this.tagList.children('li').each(function() {
if (!$(this).hasClass('tagit-new')) {
- self.createTag($(this).html(), $(this).attr('class'));
+ that.createTag($(this).html(), $(this).attr('class'));
$(this).remove();
}
});
+ // Single field support.
if (this.options.singleField) {
if (this.options.singleFieldNode) {
- // Add existing tags from the input field
+ // Add existing tags from the input field.
var node = $(this.options.singleFieldNode);
var tags = node.val().split(this.options.singleFieldDelimiter);
node.val('');
$.each(tags, function(index, tag) {
- self.createTag(tag);
+ that.createTag(tag);
});
} else {
// Create our single field input after our list.
this.options.singleFieldNode = this.tagList.after('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '">');
}
}
+ // Events.
this._tagInput
.keydown(function(event) {
// Backspace is not detected within a keypress, so it must use keydown.
- if (event.which == $.ui.keyCode.BACKSPACE && self._tagInput.val() === '') {
- var tag = self._lastTag();
- if (!self.options.removeConfirmation || tag.hasClass('remove')) {
+ if (event.which == $.ui.keyCode.BACKSPACE && that._tagInput.val() === '') {
+ var tag = that._lastTag();
+ if (!that.options.removeConfirmation || tag.hasClass('remove')) {
// When backspace is pressed, the last tag is deleted.
- self.removeTag(tag);
- } else if (self.options.removeConfirmation) {
- tag.addClass('remove');
+ that.removeTag(tag);
+ } else if (that.options.removeConfirmation) {
+ tag.addClass('remove ui-state-highlight');
}
- } else if (self.options.removeConfirmation) {
- self._lastTag().removeClass('remove');
+ } else if (that.options.removeConfirmation) {
+ that._lastTag().removeClass('remove ui-state-highlight');
}
// Comma/Space/Enter are all valid delimiters for new tags,
@@ -114,30 +127,35 @@
event.which == $.ui.keyCode.ENTER ||
(
event.which == $.ui.keyCode.TAB &&
- self._tagInput.val() !== ''
+ that._tagInput.val() !== ''
) ||
(
event.which == $.ui.keyCode.SPACE &&
- self.options.allowSpaces !== true &&
+ that.options.allowSpaces !== true &&
(
- $.trim(self._tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
+ $.trim(that._tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
(
- $.trim(self._tagInput.val()).charAt(0) == '"' &&
- $.trim(self._tagInput.val()).charAt($.trim(self._tagInput.val()).length - 1) == '"' &&
- $.trim(self._tagInput.val()).length - 1 !== 0
+ $.trim(that._tagInput.val()).charAt(0) == '"' &&
+ $.trim(that._tagInput.val()).charAt($.trim(that._tagInput.val()).length - 1) == '"' &&
+ $.trim(that._tagInput.val()).length - 1 !== 0
)
)
)
) {
event.preventDefault();
- self.createTag(self._cleanedInput());
+ that.createTag(that._cleanedInput());
+
+ // The autocomplete doesn't close automatically when TAB is pressed.
+ // So let's ensure that it closes.
+ that._tagInput.autocomplete('close');
}
}).blur(function(e){
// Create a tag when the element loses focus (unless it's empty).
- self.createTag(self._cleanedInput());
+ that.createTag(that._cleanedInput());
});
+ // Autocomplete.
if (this.options.availableTags || this.options.tagSource) {
this._tagInput.autocomplete({
source: this.options.tagSource,
@@ -148,10 +166,10 @@
// The only artifact of this is that while the user holds down the mouse button
// on the selected autocomplete item, a tag is shown with the pre-autocompleted text,
// and is changed to the autocompleted text upon mouseup.
- if (self._tagInput.val() === '') {
- self.removeTag(self._lastTag(), false);
+ if (that._tagInput.val() === '') {
+ that.removeTag(that._lastTag(), false);
}
- self.createTag(ui.item.value);
+ that.createTag(ui.item.value);
// Preventing the tag input to be updated with the chosen value.
return false;
}
@@ -170,7 +188,7 @@
assignedTags: function() {
// Returns an array of tag string values
- var self = this;
+ var that = this;
var tags = [];
if (this.options.singleField) {
tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
@@ -179,7 +197,7 @@
}
} else {
this.tagList.children('.tagit-choice').each(function() {
- tags.push(self.tagLabel(this));
+ tags.push(that.tagLabel(this));
});
}
return tags;
@@ -210,10 +228,10 @@
},
_isNew: function(value) {
- var self = this;
+ var that = this;
var isNew = true;
this.tagList.children('.tagit-choice').each(function(i) {
- if (self._formatStr(value) == self._formatStr(self.tagLabel(this))) {
+ if (that._formatStr(value) == that._formatStr(that.tagLabel(this))) {
isNew = false;
return;
}
@@ -229,12 +247,10 @@
},
createTag: function(value, additionalClass) {
+ that = this;
// Automatically trims the value of leading and trailing whitespace.
value = $.trim(value);
- // Cleaning the input.
- this._tagInput.val('');
-
if (!this._isNew(value) || value === '') {
return false;
}
@@ -246,10 +262,22 @@
.addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
.addClass(additionalClass)
.append(label);
+
+ // Button for removing the tag.
+ var removeTagIcon = $('<span></span>')
+ .addClass('ui-icon ui-icon-close');
var removeTag = $('<a></a>')
- .addClass('ui-icon ui-icon-close close');
+ .addClass('close')
+ .append(removeTagIcon)
+ .click(function(e) {
+ var target = $(e.target);
+ console.log(target);
+ // Removes a tag when the little 'x' is clicked.
+ that.removeTag(tag);
+ });
tag.append(removeTag);
+ // Unless options.singleField is set, each tag has a hidden input field inline.
if (this.options.singleField) {
var tags = this.assignedTags();
tags.push(value);
@@ -259,9 +287,10 @@
tag.append('<input type="hidden" style="display:none;" value="' + escapedValue + '" name="' + this.options.itemName + '[' + this.options.fieldName + '][]">');
}
- if (this.options.onTagAdded) {
- this.options.onTagAdded(tag);
- }
+ this._trigger('onTagAdded', null, tag);
+
+ // Cleaning the input.
+ this._tagInput.val('');
// insert tag
this._tagInput.parent().before(tag);
@@ -272,12 +301,11 @@
tag = $(tag);
- if (this.options.onTagRemoved) {
- this.options.onTagRemoved(tag);
- }
+ this._trigger('onTagRemoved', null, tag);
+
if (this.options.singleField) {
var tags = this.assignedTags();
- var removedTagLabel = tag.children('.tagit-label').text();
+ var removedTagLabel = this.tagLabel(tag);
tags = $.grep(tags, function(el){
return el != removedTagLabel;
});
@@ -295,9 +323,9 @@
removeAll: function() {
// Removes all tags. Takes an optional `animate` argument.
- var self = this;
+ var that = this;
this.tagList.children('.tagit-choice').each(function(index, tag) {
- self.removeTag(tag, false);
+ that.removeTag(tag, false);
});
}
View
BIN screenshot.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 07a7a73

Please sign in to comment.
Something went wrong with that request. Please try again.