Commit 6977150f04be1bfe365e1cd86e3f8143c34a51bf
1 parent
5d526717
Exists in
master
and in
4 other branches
integrate tags plugins
Showing
6 changed files
with
212 additions
and
0 deletions
Show diff stats
Gemfile
Gemfile.lock
... | ... | @@ -54,6 +54,8 @@ GEM |
54 | 54 | activesupport (= 3.1.0) |
55 | 55 | activesupport (3.1.0) |
56 | 56 | multi_json (~> 1.0) |
57 | + acts-as-taggable-on (2.1.1) | |
58 | + rails | |
57 | 59 | acts_as_list (0.1.4) |
58 | 60 | addressable (2.2.6) |
59 | 61 | ansi (1.3.0) |
... | ... | @@ -246,6 +248,7 @@ PLATFORMS |
246 | 248 | ruby |
247 | 249 | |
248 | 250 | DEPENDENCIES |
251 | + acts-as-taggable-on (~> 2.1.0) | |
249 | 252 | acts_as_list |
250 | 253 | annotate! |
251 | 254 | autotest | ... | ... |
app/models/project.rb
db/migrate/20111101222453_acts_as_taggable_on_migration.rb
0 → 100644
... | ... | @@ -0,0 +1,28 @@ |
1 | +class ActsAsTaggableOnMigration < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :tags do |t| | |
4 | + t.string :name | |
5 | + end | |
6 | + | |
7 | + create_table :taggings do |t| | |
8 | + t.references :tag | |
9 | + | |
10 | + # You should make sure that the column created is | |
11 | + # long enough to store the required class names. | |
12 | + t.references :taggable, :polymorphic => true | |
13 | + t.references :tagger, :polymorphic => true | |
14 | + | |
15 | + t.string :context | |
16 | + | |
17 | + t.datetime :created_at | |
18 | + end | |
19 | + | |
20 | + add_index :taggings, :tag_id | |
21 | + add_index :taggings, [:taggable_id, :taggable_type, :context] | |
22 | + end | |
23 | + | |
24 | + def self.down | |
25 | + drop_table :taggings | |
26 | + drop_table :tags | |
27 | + end | |
28 | +end | ... | ... |
... | ... | @@ -0,0 +1,143 @@ |
1 | +/* Author: Alicia Liu */ | |
2 | + | |
3 | +(function ($) { | |
4 | + | |
5 | + $.widget("ui.tagify", { | |
6 | + options: { | |
7 | + delimiters: [13, 188], // what user can type to complete a tag in char codes: [enter], [comma] | |
8 | + outputDelimiter: ',', // delimiter for tags in original input field | |
9 | + cssClass: 'tagify-container', // CSS class to style the tagify div and tags, see stylesheet | |
10 | + addTagPrompt: 'add tags' // placeholder text | |
11 | + }, | |
12 | + | |
13 | + _create: function() { | |
14 | + var self = this, | |
15 | + el = self.element, | |
16 | + opts = self.options; | |
17 | + | |
18 | + this.tags = []; | |
19 | + | |
20 | + // hide text field and replace with a div that contains it's own input field for entering tags | |
21 | + this.tagInput = $("<input type='text'>") | |
22 | + .attr( 'placeholder', opts.addTagPrompt ) | |
23 | + .keypress( function(e) { | |
24 | + var $this = $(this), | |
25 | + pressed = e.which; | |
26 | + | |
27 | + for ( i in opts.delimiters ) { | |
28 | + | |
29 | + if (pressed == opts.delimiters[i]) { | |
30 | + self.add( $this.val() ); | |
31 | + e.preventDefault(); | |
32 | + return false; | |
33 | + } | |
34 | + } | |
35 | + }) | |
36 | + // for some reason, in Safari, backspace is only recognized on keyup | |
37 | + .keyup( function(e) { | |
38 | + var $this = $(this), | |
39 | + pressed = e.which; | |
40 | + | |
41 | + // if backspace is hit with no input, remove the last tag | |
42 | + if (pressed == 8) { // backspace | |
43 | + if ( $this.val() == "" ) { | |
44 | + self.remove(); | |
45 | + return false; | |
46 | + } | |
47 | + return; | |
48 | + } | |
49 | + }); | |
50 | + | |
51 | + this.tagDiv = $("<div></div>") | |
52 | + .addClass( opts.cssClass ) | |
53 | + .click( function() { | |
54 | + $(this).children('input').focus(); | |
55 | + }) | |
56 | + .append( this.tagInput ) | |
57 | + .insertAfter( el.hide() ); | |
58 | + | |
59 | + // if the field isn't empty, parse the field for tags, and prepopulate existing tags | |
60 | + var initVal = $.trim( el.val() ); | |
61 | + | |
62 | + if ( initVal ) { | |
63 | + var initTags = initVal.split( opts.outputDelimiter ); | |
64 | + $.each( initTags, function(i, tag) { | |
65 | + self.add( tag ); | |
66 | + }); | |
67 | + } | |
68 | + }, | |
69 | + | |
70 | + _setOption: function( key, value ) { | |
71 | + options.key = value; | |
72 | + }, | |
73 | + | |
74 | + // add a tag, public function | |
75 | + add: function(text) { | |
76 | + var self = this; | |
77 | + text = text || self.tagInput.val(); | |
78 | + if (text) { | |
79 | + var tagIndex = self.tags.length; | |
80 | + | |
81 | + var removeButton = $("<a href='#'>x</a>") | |
82 | + .click( function() { | |
83 | + self.remove( tagIndex ); | |
84 | + return false; | |
85 | + }); | |
86 | + var newTag = $("<span></span>") | |
87 | + .text( text ) | |
88 | + .append( removeButton ); | |
89 | + | |
90 | + self.tagInput.before( newTag ); | |
91 | + self.tags.push( text ); | |
92 | + self.tagInput.val(''); | |
93 | + } | |
94 | + }, | |
95 | + | |
96 | + // remove a tag by index, public function | |
97 | + // if index is blank, remove the last tag | |
98 | + remove: function( tagIndex ) { | |
99 | + var self = this; | |
100 | + if ( tagIndex == null || tagIndex === (self.tags.length - 1) ) { | |
101 | + this.tagDiv.children("span").last().remove(); | |
102 | + self.tags.pop(); | |
103 | + } | |
104 | + if ( typeof(tagIndex) == 'number' ) { | |
105 | + // otherwise just hide this tag, and we don't mess up the index | |
106 | + this.tagDiv.children( "span:eq(" + tagIndex + ")" ).hide(); | |
107 | + // we rely on the serialize function to remove null values | |
108 | + delete( self.tags[tagIndex] ); | |
109 | + } | |
110 | + }, | |
111 | + | |
112 | + // serialize the tags with the given delimiter, and write it back into the tagified field | |
113 | + serialize: function() { | |
114 | + var self = this; | |
115 | + var delim = self.options.outputDelimiter; | |
116 | + var tagsStr = self.tags.join( delim ); | |
117 | + | |
118 | + // our tags might have deleted entries, remove them here | |
119 | + var dupes = new RegExp(delim + delim + '+', 'g'); // regex: /,,+/g | |
120 | + var ends = new RegExp('^' + delim + '|' + delim + '$', 'g'); // regex: /^,|,$/g | |
121 | + var outputStr = tagsStr.replace( dupes, delim ).replace(ends, ''); | |
122 | + | |
123 | + self.element.val(outputStr); | |
124 | + return outputStr; | |
125 | + }, | |
126 | + | |
127 | + inputField: function() { | |
128 | + return this.tagInput; | |
129 | + }, | |
130 | + | |
131 | + containerDiv: function() { | |
132 | + return this.tagDiv; | |
133 | + }, | |
134 | + | |
135 | + // remove the div, and show original input | |
136 | + destroy: function() { | |
137 | + $.Widget.prototype.destroy.apply(this); | |
138 | + this.tagDiv.remove(); | |
139 | + this.element.show(); | |
140 | + } | |
141 | + }); | |
142 | + | |
143 | +})(jQuery); | |
0 | 144 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,34 @@ |
1 | +/* Tagify styles | |
2 | +Author: Alicia Liu test | |
3 | +*/ | |
4 | + | |
5 | +.tagify-container { | |
6 | +} | |
7 | + | |
8 | +.tagify-container > span { | |
9 | + display: inline-block; | |
10 | + padding: 8px 11px 8px 11px; | |
11 | + margin: 1px 5px 0px 0px; | |
12 | + border-radius: 4px; | |
13 | + border: 1px solid #d0e1ff; | |
14 | + background-color: #d0e1ff; | |
15 | + color: #0f326d; | |
16 | + font-weight: bold; | |
17 | + font-size: 14px; | |
18 | +} | |
19 | + | |
20 | +.tagify-container > span > a { | |
21 | + padding-left: 5px !important; | |
22 | + color: #83a5e1; | |
23 | + text-decoration: none; | |
24 | + font-weight: bold; | |
25 | +} | |
26 | + | |
27 | +.tagify-container > input { | |
28 | + border: 0 none; | |
29 | + width: 100px !important; | |
30 | +} | |
31 | + | |
32 | +.tagify-container > input:focus { | |
33 | + outline: none; | |
34 | +} | |
0 | 35 | \ No newline at end of file | ... | ... |