├── .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 | [![Gem Version](https://badge.fury.io/rb/bootstrap-tagsinput-rails.png)](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