├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bootstrap-tagsinput-rails.gemspec
├── lib
└── bootstrap
│ └── tagsinput
│ ├── rails.rb
│ └── rails
│ └── version.rb
└── vendor
└── assets
├── javascripts
├── bootstrap-tagsinput-angular.js
├── bootstrap-tagsinput-angular.min.js
├── bootstrap-tagsinput.js
├── bootstrap-tagsinput.min.js
└── bootstrap-tagsinput.min.js.map
└── stylesheets
├── bootstrap-tagsinput.css
└── bootstrap-tagsinput.scss
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in bootstrap-tagsinput-rails.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Hyo Seong Choi
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bootstrap::Tagsinput::Rails
2 |
3 | Original Git source - https://github.com/timschlechter/bootstrap-tagsinput
4 |
5 | To gemify the assets of `bootstrap-tagsinput` jQuery plugin for Rails >= 3.1
6 |
7 | [](http://badge.fury.io/rb/bootstrap-tagsinput-rails)
8 |
9 | ## Compatibility
10 |
11 | Designed for Bootstrap 2.3.2 and 3
12 |
13 | ## Installation
14 |
15 | Add this line to your application's Gemfile:
16 |
17 | gem 'bootstrap-tagsinput-rails'
18 |
19 | And then execute:
20 |
21 | $ bundle
22 |
23 | Or install it yourself as:
24 |
25 | $ gem install bootstrap-tagsinput-rails
26 |
27 | ## Usage
28 |
29 | in app/assets/application.js
30 |
31 | ```
32 | //= require bootstrap-tagsinput
33 | ```
34 |
35 | in app/assets/application.css
36 |
37 | ```
38 | *= require bootstrap-tagsinput
39 | ```
40 |
41 | in form view, you should add `data-role='tagsinput'` within input tag as the follows: for example, in `simple-form` view template,
42 |
43 | ```
44 | <%= f.input :tag_list, input_html:{data:{role:'tagsinput'}} %>
45 | ```
46 |
47 | Or if using Rails 4 with Bootstrap, use the following,
48 |
49 | ```
50 | <%= f.text_field :tag_list, 'data-role'=>'tagsinput' %>
51 | ```
52 |
53 | That's it
54 |
55 | ## Contributing
56 |
57 | 1. Fork it
58 | 2. Create your feature branch (`git checkout -b my-new-feature`)
59 | 3. Commit your changes (`git commit -am 'Add some feature'`)
60 | 4. Push to the branch (`git push origin my-new-feature`)
61 | 5. Create new Pull Request
62 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
--------------------------------------------------------------------------------
/bootstrap-tagsinput-rails.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'bootstrap/tagsinput/rails/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "bootstrap-tagsinput-rails"
8 | spec.version = Bootstrap::Tagsinput::Rails::VERSION
9 | spec.authors = ["Hyo Seong Choi"]
10 | spec.email = ["rorlab@gmail.com"]
11 | spec.description = %q{To gemify bootstrap-tagsinput for assets pipleline}
12 | spec.summary = %q{Packaging the assets with Bunlder}
13 | spec.homepage = "http://rorlab.github.io/bootstrap-tagsinput-rails/"
14 | spec.license = "MIT"
15 |
16 | # spec.files = `git ls-files`.split($/)
17 | spec.files = Dir["{lib,vendor}/**/*"] + ["LICENSE.txt", "README.md"]
18 | # spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19 | # spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20 | spec.require_paths = ["lib"]
21 |
22 | spec.add_dependency "railties", ">= 3.1"
23 | spec.add_development_dependency "bundler", "~> 1.3"
24 | spec.add_development_dependency "rake"
25 | end
--------------------------------------------------------------------------------
/lib/bootstrap/tagsinput/rails.rb:
--------------------------------------------------------------------------------
1 | require "bootstrap/tagsinput/rails/version"
2 |
3 | module Bootstrap
4 | module Tagsinput
5 | module Rails
6 | class Engine < ::Rails::Engine
7 | end
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/bootstrap/tagsinput/rails/version.rb:
--------------------------------------------------------------------------------
1 | module Bootstrap
2 | module Tagsinput
3 | module Rails
4 | VERSION = "0.4.2.1"
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/bootstrap-tagsinput-angular.js:
--------------------------------------------------------------------------------
1 | angular.module('bootstrap-tagsinput', [])
2 | .directive('bootstrapTagsinput', [function() {
3 |
4 | function getItemProperty(scope, property) {
5 | if (!property)
6 | return undefined;
7 |
8 | if (angular.isFunction(scope.$parent[property]))
9 | return scope.$parent[property];
10 |
11 | return function(item) {
12 | return item[property];
13 | };
14 | }
15 |
16 | return {
17 | restrict: 'EA',
18 | scope: {
19 | model: '=ngModel'
20 | },
21 | template: '',
22 | replace: false,
23 | link: function(scope, element, attrs) {
24 | $(function() {
25 | if (!angular.isArray(scope.model))
26 | scope.model = [];
27 |
28 | var select = $('select', element);
29 | var typeaheadSourceArray = attrs.typeaheadSource ? attrs.typeaheadSource.split('.') : null;
30 | var typeaheadSource = typeaheadSourceArray ?
31 | (typeaheadSourceArray.length > 1 ?
32 | scope.$parent[typeaheadSourceArray[0]][typeaheadSourceArray[1]]
33 | : scope.$parent[typeaheadSourceArray[0]])
34 | : null;
35 |
36 | select.tagsinput(scope.$parent[attrs.options || ''] || {
37 | typeahead : {
38 | source : angular.isFunction(typeaheadSource) ? typeaheadSource : null
39 | },
40 | itemValue: getItemProperty(scope, attrs.itemvalue),
41 | itemText : getItemProperty(scope, attrs.itemtext),
42 | confirmKeys : getItemProperty(scope, attrs.confirmkeys) ? JSON.parse(attrs.confirmkeys) : [13],
43 | tagClass : angular.isFunction(scope.$parent[attrs.tagclass]) ? scope.$parent[attrs.tagclass] : function(item) { return attrs.tagclass; }
44 | });
45 |
46 | for (var i = 0; i < scope.model.length; i++) {
47 | select.tagsinput('add', scope.model[i]);
48 | }
49 |
50 | select.on('itemAdded', function(event) {
51 | if (scope.model.indexOf(event.item) === -1)
52 | scope.model.push(event.item);
53 | });
54 |
55 | select.on('itemRemoved', function(event) {
56 | var idx = scope.model.indexOf(event.item);
57 | if (idx !== -1)
58 | scope.model.splice(idx, 1);
59 | });
60 |
61 | // create a shallow copy of model's current state, needed to determine
62 | // diff when model changes
63 | var prev = scope.model.slice();
64 | scope.$watch("model", function() {
65 | var added = scope.model.filter(function(i) {return prev.indexOf(i) === -1;}),
66 | removed = prev.filter(function(i) {return scope.model.indexOf(i) === -1;}),
67 | i;
68 |
69 | prev = scope.model.slice();
70 |
71 | // Remove tags no longer in binded model
72 | for (i = 0; i < removed.length; i++) {
73 | select.tagsinput('remove', removed[i]);
74 | }
75 |
76 | // Refresh remaining tags
77 | select.tagsinput('refresh');
78 |
79 | // Add new items in model as tags
80 | for (i = 0; i < added.length; i++) {
81 | select.tagsinput('add', added[i]);
82 | }
83 | }, true);
84 | });
85 | }
86 | };
87 | }]);
88 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/bootstrap-tagsinput-angular.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * bootstrap-tagsinput v0.4.2 by Tim Schlechter
3 | *
4 | */
5 |
6 | angular.module("bootstrap-tagsinput",[]).directive("bootstrapTagsinput",[function(){function a(a,b){return b?angular.isFunction(a.$parent[b])?a.$parent[b]:function(a){return a[b]}:void 0}return{restrict:"EA",scope:{model:"=ngModel"},template:"",replace:!1,link:function(b,c,d){$(function(){angular.isArray(b.model)||(b.model=[]);var e=$("select",c),f=d.typeaheadSource?d.typeaheadSource.split("."):null,g=f?f.length>1?b.$parent[f[0]][f[1]]:b.$parent[f[0]]:null;e.tagsinput(b.$parent[d.options||""]||{typeahead:{source:angular.isFunction(g)?g:null},itemValue:a(b,d.itemvalue),itemText:a(b,d.itemtext),confirmKeys:a(b,d.confirmkeys)?JSON.parse(d.confirmkeys):[13],tagClass:angular.isFunction(b.$parent[d.tagclass])?b.$parent[d.tagclass]:function(){return d.tagclass}});for(var h=0;h');
42 | this.$input = $('').appendTo(this.$container);
43 |
44 | this.$element.after(this.$container);
45 |
46 | var inputWidth = (this.inputSize < 3 ? 3 : this.inputSize) + "em";
47 | this.$input.get(0).style.cssText = "width: " + inputWidth + " !important;";
48 | this.build(options);
49 | }
50 |
51 | TagsInput.prototype = {
52 | constructor: TagsInput,
53 |
54 | /**
55 | * Adds the given item as a new tag. Pass true to dontPushVal to prevent
56 | * updating the elements val()
57 | */
58 | add: function(item, dontPushVal) {
59 | var self = this;
60 |
61 | if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
62 | return;
63 |
64 | // Ignore falsey values, except false
65 | if (item !== false && !item)
66 | return;
67 |
68 | // Trim value
69 | if (typeof item === "string" && self.options.trimValue) {
70 | item = $.trim(item);
71 | }
72 |
73 | // Throw an error when trying to add an object while the itemValue option was not set
74 | if (typeof item === "object" && !self.objectItems)
75 | throw("Can't add objects when itemValue option is not set");
76 |
77 | // Ignore strings only containg whitespace
78 | if (item.toString().match(/^\s*$/))
79 | return;
80 |
81 | // If SELECT but not multiple, remove current tag
82 | if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
83 | self.remove(self.itemsArray[0]);
84 |
85 | if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
86 | var items = item.split(',');
87 | if (items.length > 1) {
88 | for (var i = 0; i < items.length; i++) {
89 | this.add(items[i], true);
90 | }
91 |
92 | if (!dontPushVal)
93 | self.pushVal();
94 | return;
95 | }
96 | }
97 |
98 | var itemValue = self.options.itemValue(item),
99 | itemText = self.options.itemText(item),
100 | tagClass = self.options.tagClass(item);
101 |
102 | // Ignore items allready added
103 | var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
104 | if (existing && !self.options.allowDuplicates) {
105 | // Invoke onTagExists
106 | if (self.options.onTagExists) {
107 | var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
108 | self.options.onTagExists(item, $existingTag);
109 | }
110 | return;
111 | }
112 |
113 | // if length greater than limit
114 | if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
115 | return;
116 |
117 | // raise beforeItemAdd arg
118 | var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false });
119 | self.$element.trigger(beforeItemAddEvent);
120 | if (beforeItemAddEvent.cancel)
121 | return;
122 |
123 | // register item in internal array and map
124 | self.itemsArray.push(item);
125 |
126 | // add a tag element
127 | var $tag = $('' + htmlEncode(itemText) + '');
128 | $tag.data('item', item);
129 | self.findInputWrapper().before($tag);
130 | $tag.after(' ');
131 |
132 | // add if item represents a value not present in one of the 's options
133 | if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
134 | var $option = $('');
135 | $option.data('item', item);
136 | $option.attr('value', itemValue);
137 | self.$element.append($option);
138 | }
139 |
140 | if (!dontPushVal)
141 | self.pushVal();
142 |
143 | // Add class when reached maxTags
144 | if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
145 | self.$container.addClass('bootstrap-tagsinput-max');
146 |
147 | self.$element.trigger($.Event('itemAdded', { item: item }));
148 | },
149 |
150 | /**
151 | * Removes the given item. Pass true to dontPushVal to prevent updating the
152 | * elements val()
153 | */
154 | remove: function(item, dontPushVal) {
155 | var self = this;
156 |
157 | if (self.objectItems) {
158 | if (typeof item === "object")
159 | item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
160 | else
161 | item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
162 |
163 | item = item[item.length-1];
164 | }
165 |
166 | if (item) {
167 | var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false });
168 | self.$element.trigger(beforeItemRemoveEvent);
169 | if (beforeItemRemoveEvent.cancel)
170 | return;
171 |
172 | $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
173 | $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
174 | if($.inArray(item, self.itemsArray) !== -1)
175 | self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
176 | }
177 |
178 | if (!dontPushVal)
179 | self.pushVal();
180 |
181 | // Remove class when reached maxTags
182 | if (self.options.maxTags > self.itemsArray.length)
183 | self.$container.removeClass('bootstrap-tagsinput-max');
184 |
185 | self.$element.trigger($.Event('itemRemoved', { item: item }));
186 | },
187 |
188 | /**
189 | * Removes all items
190 | */
191 | removeAll: function() {
192 | var self = this;
193 |
194 | $('.tag', self.$container).remove();
195 | $('option', self.$element).remove();
196 |
197 | while(self.itemsArray.length > 0)
198 | self.itemsArray.pop();
199 |
200 | self.pushVal();
201 | },
202 |
203 | /**
204 | * Refreshes the tags so they match the text/value of their corresponding
205 | * item.
206 | */
207 | refresh: function() {
208 | var self = this;
209 | $('.tag', self.$container).each(function() {
210 | var $tag = $(this),
211 | item = $tag.data('item'),
212 | itemValue = self.options.itemValue(item),
213 | itemText = self.options.itemText(item),
214 | tagClass = self.options.tagClass(item);
215 |
216 | // Update tag's class and inner text
217 | $tag.attr('class', null);
218 | $tag.addClass('tag ' + htmlEncode(tagClass));
219 | $tag.contents().filter(function() {
220 | return this.nodeType == 3;
221 | })[0].nodeValue = htmlEncode(itemText);
222 |
223 | if (self.isSelect) {
224 | var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
225 | option.attr('value', itemValue);
226 | }
227 | });
228 | },
229 |
230 | /**
231 | * Returns the items added as tags
232 | */
233 | items: function() {
234 | return this.itemsArray;
235 | },
236 |
237 | /**
238 | * Assembly value by retrieving the value of each item, and set it on the
239 | * element.
240 | */
241 | pushVal: function() {
242 | var self = this,
243 | val = $.map(self.items(), function(item) {
244 | return self.options.itemValue(item).toString();
245 | });
246 |
247 | self.$element.val(val, true).trigger('change');
248 | },
249 |
250 | /**
251 | * Initializes the tags input behaviour on the element
252 | */
253 | build: function(options) {
254 | var self = this;
255 |
256 | self.options = $.extend({}, defaultOptions, options);
257 | // When itemValue is set, freeInput should always be false
258 | if (self.objectItems)
259 | self.options.freeInput = false;
260 |
261 | makeOptionItemFunction(self.options, 'itemValue');
262 | makeOptionItemFunction(self.options, 'itemText');
263 | makeOptionFunction(self.options, 'tagClass');
264 |
265 | // Typeahead Bootstrap version 2.3.2
266 | if (self.options.typeahead) {
267 | var typeahead = self.options.typeahead || {};
268 |
269 | makeOptionFunction(typeahead, 'source');
270 |
271 | self.$input.typeahead($.extend({}, typeahead, {
272 | source: function (query, process) {
273 | function processItems(items) {
274 | var texts = [];
275 |
276 | for (var i = 0; i < items.length; i++) {
277 | var text = self.options.itemText(items[i]);
278 | map[text] = items[i];
279 | texts.push(text);
280 | }
281 | process(texts);
282 | }
283 |
284 | this.map = {};
285 | var map = this.map,
286 | data = typeahead.source(query);
287 |
288 | if ($.isFunction(data.success)) {
289 | // support for Angular callbacks
290 | data.success(processItems);
291 | } else if ($.isFunction(data.then)) {
292 | // support for Angular promises
293 | data.then(processItems);
294 | } else {
295 | // support for functions and jquery promises
296 | $.when(data)
297 | .then(processItems);
298 | }
299 | },
300 | updater: function (text) {
301 | self.add(this.map[text]);
302 | },
303 | matcher: function (text) {
304 | return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
305 | },
306 | sorter: function (texts) {
307 | return texts.sort();
308 | },
309 | highlighter: function (text) {
310 | var regex = new RegExp( '(' + this.query + ')', 'gi' );
311 | return text.replace( regex, "$1" );
312 | }
313 | }));
314 | }
315 |
316 | // typeahead.js
317 | if (self.options.typeaheadjs) {
318 | var typeaheadjs = self.options.typeaheadjs || {};
319 |
320 | self.$input.typeahead(null, typeaheadjs).on('typeahead:selected', $.proxy(function (obj, datum) {
321 | if (typeaheadjs.valueKey)
322 | self.add(datum[typeaheadjs.valueKey]);
323 | else
324 | self.add(datum);
325 | self.$input.typeahead('val', '');
326 | }, self));
327 | }
328 |
329 | self.$container.on('click', $.proxy(function(event) {
330 | if (! self.$element.attr('disabled')) {
331 | self.$input.removeAttr('disabled');
332 | }
333 | self.$input.focus();
334 | }, self));
335 |
336 | if (self.options.addOnBlur && self.options.freeInput) {
337 | self.$input.on('focusout', $.proxy(function(event) {
338 | // HACK: only process on focusout when no typeahead opened, to
339 | // avoid adding the typeahead text as tag
340 | if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
341 | self.add(self.$input.val());
342 | self.$input.val('');
343 | }
344 | }, self));
345 | }
346 |
347 |
348 | self.$container.on('keydown', 'input', $.proxy(function(event) {
349 | var $input = $(event.target),
350 | $inputWrapper = self.findInputWrapper();
351 |
352 | if (self.$element.attr('disabled')) {
353 | self.$input.attr('disabled', 'disabled');
354 | return;
355 | }
356 |
357 | switch (event.which) {
358 | // BACKSPACE
359 | case 8:
360 | if (doGetCaretPosition($input[0]) === 0) {
361 | var prev = $inputWrapper.prev();
362 | if (prev) {
363 | self.remove(prev.data('item'));
364 | }
365 | }
366 | break;
367 |
368 | // DELETE
369 | case 46:
370 | if (doGetCaretPosition($input[0]) === 0) {
371 | var next = $inputWrapper.next();
372 | if (next) {
373 | self.remove(next.data('item'));
374 | }
375 | }
376 | break;
377 |
378 | // LEFT ARROW
379 | case 37:
380 | // Try to move the input before the previous tag
381 | var $prevTag = $inputWrapper.prev();
382 | if ($input.val().length === 0 && $prevTag[0]) {
383 | $prevTag.before($inputWrapper);
384 | $input.focus();
385 | }
386 | break;
387 | // RIGHT ARROW
388 | case 39:
389 | // Try to move the input after the next tag
390 | var $nextTag = $inputWrapper.next();
391 | if ($input.val().length === 0 && $nextTag[0]) {
392 | $nextTag.after($inputWrapper);
393 | $input.focus();
394 | }
395 | break;
396 | default:
397 | // ignore
398 | }
399 |
400 | // Reset internal input's size
401 | var textLength = $input.val().length,
402 | wordSpace = Math.ceil(textLength / 5),
403 | size = textLength + wordSpace + 1;
404 | $input.attr('size', Math.max(this.inputSize, $input.val().length));
405 | }, self));
406 |
407 | self.$container.on('keypress', 'input', $.proxy(function(event) {
408 | var $input = $(event.target);
409 |
410 | if (self.$element.attr('disabled')) {
411 | self.$input.attr('disabled', 'disabled');
412 | return;
413 | }
414 |
415 | var text = $input.val(),
416 | maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
417 | if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
418 | self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
419 | $input.val('');
420 | event.preventDefault();
421 | }
422 |
423 | // Reset internal input's size
424 | var textLength = $input.val().length,
425 | wordSpace = Math.ceil(textLength / 5),
426 | size = textLength + wordSpace + 1;
427 | $input.attr('size', Math.max(this.inputSize, $input.val().length));
428 | }, self));
429 |
430 | // Remove icon clicked
431 | self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
432 | if (self.$element.attr('disabled')) {
433 | return;
434 | }
435 | self.remove($(event.target).closest('.tag').data('item'));
436 | }, self));
437 |
438 | // Only add existing value as tags when using strings as tags
439 | if (self.options.itemValue === defaultOptions.itemValue) {
440 | if (self.$element[0].tagName === 'INPUT') {
441 | self.add(self.$element.val());
442 | } else {
443 | $('option', self.$element).each(function() {
444 | self.add($(this).attr('value'), true);
445 | });
446 | }
447 | }
448 | },
449 |
450 | /**
451 | * Removes all tagsinput behaviour and unregsiter all event handlers
452 | */
453 | destroy: function() {
454 | var self = this;
455 |
456 | // Unbind events
457 | self.$container.off('keypress', 'input');
458 | self.$container.off('click', '[role=remove]');
459 |
460 | self.$container.remove();
461 | self.$element.removeData('tagsinput');
462 | self.$element.show();
463 | },
464 |
465 | /**
466 | * Sets focus on the tagsinput
467 | */
468 | focus: function() {
469 | this.$input.focus();
470 | },
471 |
472 | /**
473 | * Returns the internal input element
474 | */
475 | input: function() {
476 | return this.$input;
477 | },
478 |
479 | /**
480 | * Returns the element which is wrapped around the internal input. This
481 | * is normally the $container, but typeahead.js moves the $input element.
482 | */
483 | findInputWrapper: function() {
484 | var elt = this.$input[0],
485 | container = this.$container[0];
486 | while(elt && elt.parentNode !== container)
487 | elt = elt.parentNode;
488 |
489 | return $(elt);
490 | }
491 | };
492 |
493 | /**
494 | * Register JQuery plugin
495 | */
496 | $.fn.tagsinput = function(arg1, arg2) {
497 | var results = [];
498 |
499 | this.each(function() {
500 | var tagsinput = $(this).data('tagsinput');
501 | // Initialize a new tags input
502 | if (!tagsinput) {
503 | tagsinput = new TagsInput(this, arg1);
504 | $(this).data('tagsinput', tagsinput);
505 | results.push(tagsinput);
506 |
507 | if (this.tagName === 'SELECT') {
508 | $('option', $(this)).attr('selected', 'selected');
509 | }
510 |
511 | // Init tags from $(this).val()
512 | $(this).val($(this).val());
513 | } else if (!arg1 && !arg2) {
514 | // tagsinput already exists
515 | // no function, trying to init
516 | results.push(tagsinput);
517 | } else if(tagsinput[arg1] !== undefined) {
518 | // Invoke function on existing tags input
519 | var retVal = tagsinput[arg1](arg2);
520 | if (retVal !== undefined)
521 | results.push(retVal);
522 | }
523 | });
524 |
525 | if ( typeof arg1 == 'string') {
526 | // Return the results from the invoked function calls
527 | return results.length > 1 ? results : results[0];
528 | } else {
529 | return results;
530 | }
531 | };
532 |
533 | $.fn.tagsinput.Constructor = TagsInput;
534 |
535 | /**
536 | * Most options support both a string or number as well as a function as
537 | * option value. This function makes sure that the option with the given
538 | * key in the given options is wrapped in a function
539 | */
540 | function makeOptionItemFunction(options, key) {
541 | if (typeof options[key] !== 'function') {
542 | var propertyName = options[key];
543 | options[key] = function(item) { return item[propertyName]; };
544 | }
545 | }
546 | function makeOptionFunction(options, key) {
547 | if (typeof options[key] !== 'function') {
548 | var value = options[key];
549 | options[key] = function() { return value; };
550 | }
551 | }
552 | /**
553 | * HtmlEncodes the given value
554 | */
555 | var htmlEncodeContainer = $('');
556 | function htmlEncode(value) {
557 | if (value) {
558 | return htmlEncodeContainer.text(value).html();
559 | } else {
560 | return '';
561 | }
562 | }
563 |
564 | /**
565 | * Returns the position of the caret in the given input field
566 | * http://flightschool.acylt.com/devnotes/caret-position-woes/
567 | */
568 | function doGetCaretPosition(oField) {
569 | var iCaretPos = 0;
570 | if (document.selection) {
571 | oField.focus ();
572 | var oSel = document.selection.createRange();
573 | oSel.moveStart ('character', -oField.value.length);
574 | iCaretPos = oSel.text.length;
575 | } else if (oField.selectionStart || oField.selectionStart == '0') {
576 | iCaretPos = oField.selectionStart;
577 | }
578 | return (iCaretPos);
579 | }
580 |
581 | /**
582 | * Returns boolean indicates whether user has pressed an expected key combination.
583 | * @param object keyPressEvent: JavaScript event object, refer
584 | * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
585 | * @param object lookupList: expected key combinations, as in:
586 | * [13, {which: 188, shiftKey: true}]
587 | */
588 | function keyCombinationInList(keyPressEvent, lookupList) {
589 | var found = false;
590 | $.each(lookupList, function (index, keyCombination) {
591 | if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
592 | found = true;
593 | return false;
594 | }
595 |
596 | if (keyPressEvent.which === keyCombination.which) {
597 | var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
598 | shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
599 | ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
600 | if (alt && shift && ctrl) {
601 | found = true;
602 | return false;
603 | }
604 | }
605 | });
606 |
607 | return found;
608 | }
609 |
610 | /**
611 | * Initialize tagsinput behaviour on inputs and selects which have
612 | * data-role=tagsinput
613 | */
614 | $(function() {
615 | $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
616 | });
617 | })(window.jQuery);
618 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/bootstrap-tagsinput.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * bootstrap-tagsinput v0.4.2 by Tim Schlechter
3 | *
4 | */
5 |
6 | !function(a){"use strict";function b(b,c){this.itemsArray=[],this.$element=a(b),this.$element.hide(),this.isSelect="SELECT"===b.tagName,this.multiple=this.isSelect&&b.hasAttribute("multiple"),this.objectItems=c&&c.itemValue,this.placeholderText=b.hasAttribute("placeholder")?this.$element.attr("placeholder"):"",this.inputSize=Math.max(1,this.placeholderText.length),this.$container=a(''),this.$input=a('').appendTo(this.$container),this.$element.after(this.$container);var d=(this.inputSize<3?3:this.inputSize)+"em";this.$input.get(0).style.cssText="width: "+d+" !important;",this.build(c)}function c(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(a){return a[c]}}}function d(a,b){if("function"!=typeof a[b]){var c=a[b];a[b]=function(){return c}}}function e(a){return a?i.text(a).html():""}function f(a){var b=0;if(document.selection){a.focus();var c=document.selection.createRange();c.moveStart("character",-a.value.length),b=c.text.length}else(a.selectionStart||"0"==a.selectionStart)&&(b=a.selectionStart);return b}function g(b,c){var d=!1;return a.each(c,function(a,c){if("number"==typeof c&&b.which===c)return d=!0,!1;if(b.which===c.which){var e=!c.hasOwnProperty("altKey")||b.altKey===c.altKey,f=!c.hasOwnProperty("shiftKey")||b.shiftKey===c.shiftKey,g=!c.hasOwnProperty("ctrlKey")||b.ctrlKey===c.ctrlKey;if(e&&f&&g)return d=!0,!1}}),d}var h={tagClass:function(){return"label label-info"},itemValue:function(a){return a?a.toString():a},itemText:function(a){return this.itemValue(a)},freeInput:!0,addOnBlur:!0,maxTags:void 0,maxChars:void 0,confirmKeys:[13,44],onTagExists:function(a,b){b.hide().fadeIn()},trimValue:!1,allowDuplicates:!1};b.prototype={constructor:b,add:function(b,c){var d=this;if(!(d.options.maxTags&&d.itemsArray.length>=d.options.maxTags||b!==!1&&!b)){if("string"==typeof b&&d.options.trimValue&&(b=a.trim(b)),"object"==typeof b&&!d.objectItems)throw"Can't add objects when itemValue option is not set";if(!b.toString().match(/^\s*$/)){if(d.isSelect&&!d.multiple&&d.itemsArray.length>0&&d.remove(d.itemsArray[0]),"string"==typeof b&&"INPUT"===this.$element[0].tagName){var f=b.split(",");if(f.length>1){for(var g=0;gd.options.maxInputLength)){var l=a.Event("beforeItemAdd",{item:b,cancel:!1});if(d.$element.trigger(l),!l.cancel){d.itemsArray.push(b);var m=a(''+e(i)+'');if(m.data("item",b),d.findInputWrapper().before(m),m.after(" "),d.isSelect&&!a('option[value="'+encodeURIComponent(h)+'"]',d.$element)[0]){var n=a("");n.data("item",b),n.attr("value",h),d.$element.append(n)}c||d.pushVal(),(d.options.maxTags===d.itemsArray.length||d.items().toString().length===d.options.maxInputLength)&&d.$container.addClass("bootstrap-tagsinput-max"),d.$element.trigger(a.Event("itemAdded",{item:b}))}}}else if(d.options.onTagExists){var o=a(".tag",d.$container).filter(function(){return a(this).data("item")===k});d.options.onTagExists(b,o)}}}},remove:function(b,c){var d=this;if(d.objectItems&&(b="object"==typeof b?a.grep(d.itemsArray,function(a){return d.options.itemValue(a)==d.options.itemValue(b)}):a.grep(d.itemsArray,function(a){return d.options.itemValue(a)==b}),b=b[b.length-1]),b){var e=a.Event("beforeItemRemove",{item:b,cancel:!1});if(d.$element.trigger(e),e.cancel)return;a(".tag",d.$container).filter(function(){return a(this).data("item")===b}).remove(),a("option",d.$element).filter(function(){return a(this).data("item")===b}).remove(),-1!==a.inArray(b,d.itemsArray)&&d.itemsArray.splice(a.inArray(b,d.itemsArray),1)}c||d.pushVal(),d.options.maxTags>d.itemsArray.length&&d.$container.removeClass("bootstrap-tagsinput-max"),d.$element.trigger(a.Event("itemRemoved",{item:b}))},removeAll:function(){var b=this;for(a(".tag",b.$container).remove(),a("option",b.$element).remove();b.itemsArray.length>0;)b.itemsArray.pop();b.pushVal()},refresh:function(){var b=this;a(".tag",b.$container).each(function(){var c=a(this),d=c.data("item"),f=b.options.itemValue(d),g=b.options.itemText(d),h=b.options.tagClass(d);if(c.attr("class",null),c.addClass("tag "+e(h)),c.contents().filter(function(){return 3==this.nodeType})[0].nodeValue=e(g),b.isSelect){var i=a("option",b.$element).filter(function(){return a(this).data("item")===d});i.attr("value",f)}})},items:function(){return this.itemsArray},pushVal:function(){var b=this,c=a.map(b.items(),function(a){return b.options.itemValue(a).toString()});b.$element.val(c,!0).trigger("change")},build:function(b){var e=this;if(e.options=a.extend({},h,b),e.objectItems&&(e.options.freeInput=!1),c(e.options,"itemValue"),c(e.options,"itemText"),d(e.options,"tagClass"),e.options.typeahead){var i=e.options.typeahead||{};d(i,"source"),e.$input.typeahead(a.extend({},i,{source:function(b,c){function d(a){for(var b=[],d=0;d$1")}}))}if(e.options.typeaheadjs){var j=e.options.typeaheadjs||{};e.$input.typeahead(null,j).on("typeahead:selected",a.proxy(function(a,b){e.add(j.valueKey?b[j.valueKey]:b),e.$input.typeahead("val","")},e))}e.$container.on("click",a.proxy(function(){e.$element.attr("disabled")||e.$input.removeAttr("disabled"),e.$input.focus()},e)),e.options.addOnBlur&&e.options.freeInput&&e.$input.on("focusout",a.proxy(function(){0===a(".typeahead, .twitter-typeahead",e.$container).length&&(e.add(e.$input.val()),e.$input.val(""))},e)),e.$container.on("keydown","input",a.proxy(function(b){var c=a(b.target),d=e.findInputWrapper();if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");switch(b.which){case 8:if(0===f(c[0])){var g=d.prev();g&&e.remove(g.data("item"))}break;case 46:if(0===f(c[0])){var h=d.next();h&&e.remove(h.data("item"))}break;case 37:var i=d.prev();0===c.val().length&&i[0]&&(i.before(d),c.focus());break;case 39:var j=d.next();0===c.val().length&&j[0]&&(j.after(d),c.focus())}{var k=c.val().length;Math.ceil(k/5)}c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("keypress","input",a.proxy(function(b){var c=a(b.target);if(e.$element.attr("disabled"))return void e.$input.attr("disabled","disabled");var d=c.val(),f=e.options.maxChars&&d.length>=e.options.maxChars;e.options.freeInput&&(g(b,e.options.confirmKeys)||f)&&(e.add(f?d.substr(0,e.options.maxChars):d),c.val(""),b.preventDefault());{var h=c.val().length;Math.ceil(h/5)}c.attr("size",Math.max(this.inputSize,c.val().length))},e)),e.$container.on("click","[data-role=remove]",a.proxy(function(b){e.$element.attr("disabled")||e.remove(a(b.target).closest(".tag").data("item"))},e)),e.options.itemValue===h.itemValue&&("INPUT"===e.$element[0].tagName?e.add(e.$element.val()):a("option",e.$element).each(function(){e.add(a(this).attr("value"),!0)}))},destroy:function(){var a=this;a.$container.off("keypress","input"),a.$container.off("click","[role=remove]"),a.$container.remove(),a.$element.removeData("tagsinput"),a.$element.show()},focus:function(){this.$input.focus()},input:function(){return this.$input},findInputWrapper:function(){for(var b=this.$input[0],c=this.$container[0];b&&b.parentNode!==c;)b=b.parentNode;return a(b)}},a.fn.tagsinput=function(c,d){var e=[];return this.each(function(){var f=a(this).data("tagsinput");if(f)if(c||d){if(void 0!==f[c]){var g=f[c](d);void 0!==g&&e.push(g)}}else e.push(f);else f=new b(this,c),a(this).data("tagsinput",f),e.push(f),"SELECT"===this.tagName&&a("option",a(this)).attr("selected","selected"),a(this).val(a(this).val())}),"string"==typeof c?e.length>1?e:e[0]:e},a.fn.tagsinput.Constructor=b;var i=a("");a(function(){a("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput()})}(window.jQuery);
7 | //# sourceMappingURL=bootstrap-tagsinput.min.js.map
--------------------------------------------------------------------------------
/vendor/assets/javascripts/bootstrap-tagsinput.min.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"dist/bootstrap-tagsinput-angular.min.js","sources":["src/bootstrap-tagsinput-angular.js"],"names":["angular","module","directive","getItemProperty","scope","property","isFunction","$parent","item","undefined","restrict","model","template","replace","link","element","attrs","$","isArray","select","typeaheadSourceArray","typeaheadSource","split","length","tagsinput","options","typeahead","source","itemValue","itemvalue","itemText","itemtext","confirmKeys","confirmkeys","JSON","parse","tagClass","tagclass","i","on","event","indexOf","push","idx","splice","prev","slice","$watch","added","filter","removed"],"mappings":";;;;;AAAAA,QAAQC,OAAO,0BACdC,UAAU,sBAAuB,WAEhC,QAASC,GAAgBC,EAAOC,GAC9B,MAAKA,GAGDL,QAAQM,WAAWF,EAAMG,QAAQF,IAC5BD,EAAMG,QAAQF,GAEhB,SAASG,GACd,MAAOA,GAAKH,IANLI,OAUX,OACEC,SAAU,KACVN,OACEO,MAAO,YAETC,SAAU,6BACVC,SAAS,EACTC,KAAM,SAASV,EAAOW,EAASC,GAC7BC,EAAE,WACKjB,QAAQkB,QAAQd,EAAMO,SACzBP,EAAMO,SAER,IAAIQ,GAASF,EAAE,SAAUF,GACrBK,EAAuBJ,EAAMK,gBAAkBL,EAAMK,gBAAgBC,MAAM,KAAO,KAClFD,EAAkBD,EACjBA,EAAqBG,OAAS,EAC3BnB,EAAMG,QAAQa,EAAqB,IAAIA,EAAqB,IAC1DhB,EAAMG,QAAQa,EAAqB,IACvC,IAEND,GAAOK,UAAUpB,EAAMG,QAAQS,EAAMS,SAAW,MAC9CC,WACEC,OAAW3B,QAAQM,WAAWe,GAAmBA,EAAkB,MAErEO,UAAWzB,EAAgBC,EAAOY,EAAMa,WACxCC,SAAW3B,EAAgBC,EAAOY,EAAMe,UACxCC,YAAc7B,EAAgBC,EAAOY,EAAMiB,aAAeC,KAAKC,MAAMnB,EAAMiB,cAAgB,IAC3FG,SAAWpC,QAAQM,WAAWF,EAAMG,QAAQS,EAAMqB,WAAajC,EAAMG,QAAQS,EAAMqB,UAAY,WAAiB,MAAOrB,GAAMqB,WAG/H,KAAK,GAAIC,GAAI,EAAGA,EAAIlC,EAAMO,MAAMY,OAAQe,IACtCnB,EAAOK,UAAU,MAAOpB,EAAMO,MAAM2B,GAGtCnB,GAAOoB,GAAG,YAAa,SAASC,GACU,KAApCpC,EAAMO,MAAM8B,QAAQD,EAAMhC,OAC5BJ,EAAMO,MAAM+B,KAAKF,EAAMhC,QAG3BW,EAAOoB,GAAG,cAAe,SAASC,GAChC,GAAIG,GAAMvC,EAAMO,MAAM8B,QAAQD,EAAMhC,KACxB,MAARmC,GACFvC,EAAMO,MAAMiC,OAAOD,EAAK,IAK5B,IAAIE,GAAOzC,EAAMO,MAAMmC,OACvB1C,GAAM2C,OAAO,QAAS,WACpB,GAEIT,GAFAU,EAAQ5C,EAAMO,MAAMsC,OAAO,SAASX,GAAI,MAA2B,KAApBO,EAAKJ,QAAQH,KAC5DY,EAAUL,EAAKI,OAAO,SAASX,GAAI,MAAkC,KAA3BlC,EAAMO,MAAM8B,QAAQH,IAMlE,KAHAO,EAAOzC,EAAMO,MAAMmC,QAGdR,EAAI,EAAGA,EAAIY,EAAQ3B,OAAQe,IAC9BnB,EAAOK,UAAU,SAAU0B,EAAQZ,GAOrC,KAHAnB,EAAOK,UAAU,WAGZc,EAAI,EAAGA,EAAIU,EAAMzB,OAAQe,IAC5BnB,EAAOK,UAAU,MAAOwB,EAAMV,MAE/B"}
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/bootstrap-tagsinput.css:
--------------------------------------------------------------------------------
1 | .bootstrap-tagsinput {
2 | background-color: #fff;
3 | border: 1px solid #ccc;
4 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
5 | display: inline-block;
6 | padding: 4px 6px;
7 | margin-bottom: 10px;
8 | color: #555;
9 | vertical-align: middle;
10 | border-radius: 4px;
11 | max-width: 100%;
12 | line-height: 22px;
13 | cursor: text;
14 | }
15 | .bootstrap-tagsinput input {
16 | border: none;
17 | box-shadow: none;
18 | outline: none;
19 | background-color: transparent;
20 | padding: 0;
21 | margin: 0;
22 | width: auto !important;
23 | max-width: inherit;
24 | }
25 | .bootstrap-tagsinput input:focus {
26 | border: none;
27 | box-shadow: none;
28 | }
29 | .bootstrap-tagsinput .tag {
30 | margin-right: 2px;
31 | color: white;
32 | }
33 | .bootstrap-tagsinput .tag [data-role="remove"] {
34 | margin-left: 8px;
35 | cursor: pointer;
36 | }
37 | .bootstrap-tagsinput .tag [data-role="remove"]:after {
38 | content: "x";
39 | padding: 0px 2px;
40 | }
41 | .bootstrap-tagsinput .tag [data-role="remove"]:hover {
42 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
43 | }
44 | .bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
45 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
46 | }
47 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/bootstrap-tagsinput.scss:
--------------------------------------------------------------------------------
1 | .bootstrap-tagsinput {
2 | background-color: #fff;
3 | border: 1px solid #ccc;
4 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
5 | display: inline-block;
6 | padding: 4px 6px;
7 | margin-bottom: 10px;
8 | color: #555;
9 | vertical-align: middle;
10 | border-radius: 4px;
11 | max-width: 100%;
12 | line-height: 22px;
13 | cursor: text;
14 |
15 | input {
16 | border: none;
17 | box-shadow: none;
18 | outline: none;
19 | background-color: transparent;
20 | padding: 0;
21 | margin: 0;
22 | width: auto !important;
23 | max-width: inherit;
24 |
25 | &:focus {
26 | border: none;
27 | box-shadow: none;
28 | }
29 | }
30 |
31 | .tag {
32 | margin-right: 2px;
33 | color: white;
34 |
35 | [data-role="remove"] {
36 | margin-left:8px;
37 | cursor:pointer;
38 | &:after{
39 | content: "x";
40 | padding:0px 2px;
41 | }
42 | &:hover {
43 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
44 | &:active {
45 | box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------