locomotivecms 3.0.0.rc4 → 3.0.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/app/api/locomotive/api/resources/content_type_resource.rb +16 -1
  4. data/app/api/locomotive/api/resources/page_resource.rb +9 -4
  5. data/app/api/locomotive/api/resources/snippet_resource.rb +17 -2
  6. data/app/api/locomotive/api/resources/theme_asset_resource.rb +11 -0
  7. data/app/api/locomotive/api/resources/translation_resource.rb +16 -1
  8. data/app/assets/javascripts/locomotive.js +1 -0
  9. data/app/assets/javascripts/locomotive/{not_logged_in.js.coffee → account.js.coffee} +1 -0
  10. data/app/assets/javascripts/locomotive/views/shared/form_view.js.coffee +2 -2
  11. data/app/assets/stylesheets/locomotive/{unauthorized.scss → account.scss} +7 -8
  12. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_forms.scss +19 -0
  13. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_keyframes.scss +0 -0
  14. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_notify.scss +3 -1
  15. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_public.scss +98 -10
  16. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_type.scss +0 -0
  17. data/app/assets/stylesheets/locomotive/{unauthorized → account}/_variables.scss +1 -1
  18. data/app/assets/stylesheets/locomotive/application.scss +1 -1
  19. data/app/assets/stylesheets/locomotive/base/_form.scss +1 -0
  20. data/app/assets/stylesheets/locomotive/base/form/_tags.scss +17 -0
  21. data/app/assets/stylesheets/locomotive/error.scss +1 -1
  22. data/app/assets/stylesheets/locomotive/layouts/_error.scss +33 -0
  23. data/app/controllers/locomotive/concerns/site_dispatcher_controller.rb +1 -2
  24. data/app/controllers/locomotive/passwords_controller.rb +1 -1
  25. data/app/controllers/locomotive/registrations_controller.rb +1 -1
  26. data/app/controllers/locomotive/sessions_controller.rb +1 -1
  27. data/app/controllers/locomotive/sites_controller.rb +1 -1
  28. data/app/helpers/locomotive/custom_fields_helper.rb +8 -2
  29. data/app/models/locomotive/editable_element.rb +2 -2
  30. data/app/policies/locomotive/application_policy.rb +4 -0
  31. data/app/policies/locomotive/content_type_policy.rb +4 -0
  32. data/app/policies/locomotive/site_policy.rb +1 -1
  33. data/app/policies/locomotive/snippet_policy.rb +4 -0
  34. data/app/policies/locomotive/theme_asset_policy.rb +4 -0
  35. data/app/policies/locomotive/translation_policy.rb +4 -0
  36. data/app/views/locomotive/current_site/_membership.html.slim +20 -19
  37. data/app/views/locomotive/layouts/{not_logged_in.html.slim → account.html.slim} +8 -3
  38. data/app/views/locomotive/passwords/edit.html.slim +2 -2
  39. data/app/views/locomotive/passwords/new.html.slim +2 -4
  40. data/app/views/locomotive/registrations/new.html.slim +2 -2
  41. data/app/views/locomotive/sessions/new.html.slim +2 -2
  42. data/app/views/locomotive/shared/header/_site.html.slim +1 -1
  43. data/app/views/locomotive/sites/_navigation.slim +5 -0
  44. data/app/views/locomotive/sites/_site.html.slim +6 -16
  45. data/app/views/locomotive/sites/index.html.slim +12 -19
  46. data/app/views/locomotive/sites/new.html.slim +13 -8
  47. data/config/locales/en.yml +8 -4
  48. data/lib/locomotive/engine.rb +2 -2
  49. data/lib/locomotive/version.rb +1 -1
  50. data/spec/lib/locomotive/steam/services/liquid_parser_with_cache_service_spec.rb +1 -1
  51. data/spec/models/locomotive/content_entry_spec.rb +1 -1
  52. data/spec/models/locomotive/editable_element_spec.rb +26 -0
  53. data/spec/requests/locomotive/steam/cache_spec.rb +1 -1
  54. data/spec/support/capybara.rb +3 -3
  55. data/spec/support/features/session_helpers.rb +2 -3
  56. data/vendor/assets/{stylesheets/locomotive → components/locomotive/bootstrap-tagsinput}/bootstrap-tagsinput.css +16 -7
  57. data/vendor/assets/components/locomotive/bootstrap-tagsinput/bootstrap-tagsinput.js +663 -0
  58. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/Gruntfile.js +82 -0
  59. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/LICENSE +20 -0
  60. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/README.md +92 -0
  61. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/bootstrap-tagsinput.jquery.json +27 -0
  62. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/bower.json +42 -0
  63. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput-angular.js +87 -0
  64. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput-angular.min.js +7 -0
  65. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput-angular.min.js.map +1 -0
  66. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput-typeahead.css +49 -0
  67. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.css +55 -0
  68. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.js +663 -0
  69. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.less +50 -0
  70. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.min.js +7 -0
  71. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.min.js.map +1 -0
  72. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/dist/bootstrap-tagsinput.zip +0 -0
  73. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/app.css +67 -0
  74. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/app.js +15 -0
  75. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/app_bs2.js +74 -0
  76. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/app_bs3.js +95 -0
  77. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/cities.json +16 -0
  78. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/assets/citynames.json +16 -0
  79. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/bootstrap-2.3.2.html +666 -0
  80. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/examples/index.html +742 -0
  81. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/karma.conf.js +20 -0
  82. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/package.json +47 -0
  83. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/src/bootstrap-tagsinput-angular.js +87 -0
  84. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/src/bootstrap-tagsinput-typeahead.css +49 -0
  85. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/src/bootstrap-tagsinput.css +55 -0
  86. data/vendor/assets/components/locomotive_sources/bootstrap-tagsinput/src/bootstrap-tagsinput.js +663 -0
  87. data/vendor/assets/components/locomotive_sources/jquery/MIT-LICENSE.txt +21 -0
  88. data/vendor/assets/components/locomotive_sources/jquery/bower.json +28 -0
  89. data/vendor/assets/components/locomotive_sources/jquery/dist/jquery.js +9210 -0
  90. data/vendor/assets/components/locomotive_sources/jquery/dist/jquery.min.js +5 -0
  91. data/vendor/assets/components/locomotive_sources/jquery/dist/jquery.min.map +1 -0
  92. data/vendor/assets/components/locomotive_sources/jquery/src/ajax.js +786 -0
  93. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/jsonp.js +89 -0
  94. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/load.js +75 -0
  95. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/parseJSON.js +13 -0
  96. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/parseXML.js +28 -0
  97. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/script.js +64 -0
  98. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/var/nonce.js +5 -0
  99. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/var/rquery.js +3 -0
  100. data/vendor/assets/components/locomotive_sources/jquery/src/ajax/xhr.js +136 -0
  101. data/vendor/assets/components/locomotive_sources/jquery/src/attributes.js +11 -0
  102. data/vendor/assets/components/locomotive_sources/jquery/src/attributes/attr.js +141 -0
  103. data/vendor/assets/components/locomotive_sources/jquery/src/attributes/classes.js +158 -0
  104. data/vendor/assets/components/locomotive_sources/jquery/src/attributes/prop.js +94 -0
  105. data/vendor/assets/components/locomotive_sources/jquery/src/attributes/support.js +35 -0
  106. data/vendor/assets/components/locomotive_sources/jquery/src/attributes/val.js +161 -0
  107. data/vendor/assets/components/locomotive_sources/jquery/src/callbacks.js +205 -0
  108. data/vendor/assets/components/locomotive_sources/jquery/src/core.js +502 -0
  109. data/vendor/assets/components/locomotive_sources/jquery/src/core/access.js +60 -0
  110. data/vendor/assets/components/locomotive_sources/jquery/src/core/init.js +123 -0
  111. data/vendor/assets/components/locomotive_sources/jquery/src/core/parseHTML.js +39 -0
  112. data/vendor/assets/components/locomotive_sources/jquery/src/core/ready.js +97 -0
  113. data/vendor/assets/components/locomotive_sources/jquery/src/core/var/rsingleTag.js +4 -0
  114. data/vendor/assets/components/locomotive_sources/jquery/src/css.js +450 -0
  115. data/vendor/assets/components/locomotive_sources/jquery/src/css/addGetHookIf.js +22 -0
  116. data/vendor/assets/components/locomotive_sources/jquery/src/css/curCSS.js +57 -0
  117. data/vendor/assets/components/locomotive_sources/jquery/src/css/defaultDisplay.js +70 -0
  118. data/vendor/assets/components/locomotive_sources/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  119. data/vendor/assets/components/locomotive_sources/jquery/src/css/support.js +96 -0
  120. data/vendor/assets/components/locomotive_sources/jquery/src/css/swap.js +28 -0
  121. data/vendor/assets/components/locomotive_sources/jquery/src/css/var/cssExpand.js +3 -0
  122. data/vendor/assets/components/locomotive_sources/jquery/src/css/var/getStyles.js +12 -0
  123. data/vendor/assets/components/locomotive_sources/jquery/src/css/var/isHidden.js +13 -0
  124. data/vendor/assets/components/locomotive_sources/jquery/src/css/var/rmargin.js +3 -0
  125. data/vendor/assets/components/locomotive_sources/jquery/src/css/var/rnumnonpx.js +5 -0
  126. data/vendor/assets/components/locomotive_sources/jquery/src/data.js +178 -0
  127. data/vendor/assets/components/locomotive_sources/jquery/src/data/Data.js +181 -0
  128. data/vendor/assets/components/locomotive_sources/jquery/src/data/accepts.js +20 -0
  129. data/vendor/assets/components/locomotive_sources/jquery/src/data/var/data_priv.js +5 -0
  130. data/vendor/assets/components/locomotive_sources/jquery/src/data/var/data_user.js +5 -0
  131. data/vendor/assets/components/locomotive_sources/jquery/src/deferred.js +149 -0
  132. data/vendor/assets/components/locomotive_sources/jquery/src/deprecated.js +13 -0
  133. data/vendor/assets/components/locomotive_sources/jquery/src/dimensions.js +50 -0
  134. data/vendor/assets/components/locomotive_sources/jquery/src/effects.js +648 -0
  135. data/vendor/assets/components/locomotive_sources/jquery/src/effects/Tween.js +114 -0
  136. data/vendor/assets/components/locomotive_sources/jquery/src/effects/animatedSelector.js +13 -0
  137. data/vendor/assets/components/locomotive_sources/jquery/src/event.js +868 -0
  138. data/vendor/assets/components/locomotive_sources/jquery/src/event/ajax.js +13 -0
  139. data/vendor/assets/components/locomotive_sources/jquery/src/event/alias.js +39 -0
  140. data/vendor/assets/components/locomotive_sources/jquery/src/event/support.js +9 -0
  141. data/vendor/assets/components/locomotive_sources/jquery/src/exports/amd.js +24 -0
  142. data/vendor/assets/components/locomotive_sources/jquery/src/exports/global.js +32 -0
  143. data/vendor/assets/components/locomotive_sources/jquery/src/intro.js +44 -0
  144. data/vendor/assets/components/locomotive_sources/jquery/src/jquery.js +37 -0
  145. data/vendor/assets/components/locomotive_sources/jquery/src/manipulation.js +580 -0
  146. data/vendor/assets/components/locomotive_sources/jquery/src/manipulation/_evalUrl.js +18 -0
  147. data/vendor/assets/components/locomotive_sources/jquery/src/manipulation/support.js +32 -0
  148. data/vendor/assets/components/locomotive_sources/jquery/src/manipulation/var/rcheckableType.js +3 -0
  149. data/vendor/assets/components/locomotive_sources/jquery/src/offset.js +207 -0
  150. data/vendor/assets/components/locomotive_sources/jquery/src/outro.js +1 -0
  151. data/vendor/assets/components/locomotive_sources/jquery/src/queue.js +142 -0
  152. data/vendor/assets/components/locomotive_sources/jquery/src/queue/delay.js +22 -0
  153. data/vendor/assets/components/locomotive_sources/jquery/src/selector-native.js +172 -0
  154. data/vendor/assets/components/locomotive_sources/jquery/src/selector-sizzle.js +14 -0
  155. data/vendor/assets/components/locomotive_sources/jquery/src/selector.js +1 -0
  156. data/vendor/assets/components/locomotive_sources/jquery/src/serialize.js +111 -0
  157. data/vendor/assets/components/locomotive_sources/jquery/src/sizzle/dist/sizzle.js +2067 -0
  158. data/vendor/assets/components/locomotive_sources/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  159. data/vendor/assets/components/locomotive_sources/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  160. data/vendor/assets/components/locomotive_sources/jquery/src/traversing.js +199 -0
  161. data/vendor/assets/components/locomotive_sources/jquery/src/traversing/findFilter.js +100 -0
  162. data/vendor/assets/components/locomotive_sources/jquery/src/traversing/var/rneedsContext.js +6 -0
  163. data/vendor/assets/components/locomotive_sources/jquery/src/var/arr.js +3 -0
  164. data/vendor/assets/components/locomotive_sources/jquery/src/var/class2type.js +4 -0
  165. data/vendor/assets/components/locomotive_sources/jquery/src/var/concat.js +5 -0
  166. data/vendor/assets/components/locomotive_sources/jquery/src/var/hasOwn.js +5 -0
  167. data/vendor/assets/components/locomotive_sources/jquery/src/var/indexOf.js +5 -0
  168. data/vendor/assets/components/locomotive_sources/jquery/src/var/pnum.js +3 -0
  169. data/vendor/assets/components/locomotive_sources/jquery/src/var/push.js +5 -0
  170. data/vendor/assets/components/locomotive_sources/jquery/src/var/rnotwhite.js +3 -0
  171. data/vendor/assets/components/locomotive_sources/jquery/src/var/slice.js +5 -0
  172. data/vendor/assets/components/locomotive_sources/jquery/src/var/strundefined.js +3 -0
  173. data/vendor/assets/components/locomotive_sources/jquery/src/var/support.js +4 -0
  174. data/vendor/assets/components/locomotive_sources/jquery/src/var/toString.js +5 -0
  175. data/vendor/assets/components/locomotive_sources/jquery/src/wrap.js +79 -0
  176. metadata +138 -23
  177. data/app/assets/stylesheets/locomotive/base/not_logged_in/_all.scss +0 -4
  178. data/app/assets/stylesheets/locomotive/base/not_logged_in/_buttons.scss +0 -10
  179. data/app/assets/stylesheets/locomotive/base/not_logged_in/_form.scss +0 -72
  180. data/app/assets/stylesheets/locomotive/base/not_logged_in/_messages.scss +0 -4
  181. data/app/assets/stylesheets/locomotive/base/not_logged_in/_typography.scss +0 -12
  182. data/app/assets/stylesheets/locomotive/components/not_logged_in/_passwords.scss +0 -9
  183. data/app/assets/stylesheets/locomotive/components/not_logged_in/_sign_in_and_up.scss +0 -34
  184. data/app/models/locomotive/concerns/page/render.rb +0 -99
  185. data/vendor/assets/javascripts/locomotive/bootstrap-tagsinput.min.js +0 -6
@@ -1,12 +1,17 @@
1
- - content_for :site_title do
2
- = t('.title')
1
+ - title t('.title')
3
2
 
4
- = help t('.help', default: '')
3
+ .public-box
4
+ .public-intro
5
+ h3= t('.title')
6
+ p= t('.description')
5
7
 
6
- = locomotive_form_for @site, url: sites_path, html: { multipart: true } do |f|
8
+ = locomotive_form_for @site, url: sites_path, html: { multipart: true } do |f|
9
+ = f.inputs :information do
10
+ = f.input :name
11
+ = f.input :handle
7
12
 
8
- = f.inputs :information do
9
- = f.input :name, input_html: { class: 'input-lg' }
10
- = f.input :handle
13
+ = f.actions do
14
+ = link_to t('.go_back').html_safe, sites_path, class: 'form-link'
15
+ = f.button :submit, t('.submit').html_safe, class: 'btn btn-primary', data: { loading_text: t('simple_form.buttons.defaults.locomotive.loading_text') }
11
16
 
12
- = f.actions back_url: sites_path
17
+ = render 'locomotive/sites/navigation'
@@ -225,13 +225,17 @@ en:
225
225
 
226
226
  sites:
227
227
  index:
228
- title: My sites
228
+ description: "All sites you have access to can be found below. Click one to be redirected to the content management section of that site."
229
229
  new: Add a new site
230
+ title: My sites
230
231
  new:
231
- title: New site
232
- help: "Fill in the form below to create your new site."
232
+ description: "Fill in the form below and click submit. Once successfull it will create a new site."
233
+ go_back: Go back
234
+ submit: Create site
235
+ title: Create new site
236
+
233
237
  site:
234
- no_domain: No domain
238
+ no_domain: No domain yet
235
239
 
236
240
  current_site:
237
241
  edit:
@@ -44,8 +44,8 @@ module Locomotive
44
44
  locomotive/wysihtml5_editor.css
45
45
  locomotive.js
46
46
  locomotive.css
47
- locomotive/not_logged_in.js
48
- locomotive/unauthorized.css
47
+ locomotive/account.js
48
+ locomotive/account.css
49
49
  locomotive/live_editing_iframe.css
50
50
  locomotive/live_editing_error.css
51
51
  locomotive/error.css)
@@ -2,5 +2,5 @@
2
2
  # MAJOR.MINOR.PATCH format.
3
3
  # 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
4
4
  module Locomotive #:nodoc
5
- VERSION = '3.0.0.rc4'
5
+ VERSION = '3.0.0.rc5'
6
6
  end
@@ -19,7 +19,7 @@ describe Locomotive::Steam::LiquidParserWithCacheService do
19
19
 
20
20
  describe '#parse' do
21
21
 
22
- let(:parent) { instance_double('ParsedPage', liquid_source: 'Hello {% block content %}!{% endblock %}') }
22
+ let(:parent) { instance_double('ParsedParentPage', liquid_source: 'Hello {% block content %}!{% endblock %}', handle: nil, slug: nil) }
23
23
  let(:page) { instance_double('ParsedPage', _id: '0001', liquid_source: '{% extends parent %}{% block content %}world{{ block.super}}{% endblock %}') }
24
24
  let(:parent_finder) { instance_double('ParentFinder', find: parent) }
25
25
 
@@ -169,7 +169,7 @@ describe Locomotive::ContentEntry do
169
169
 
170
170
  subject { @content_type.ordered_entries.to_csv(host: 'example.com').split("\n") }
171
171
 
172
- it { puts @content_type.ordered_entries.first.content_type.inspect; expect(subject.size).to eq(4) }
172
+ it { expect(subject.size).to eq(4) }
173
173
  it { expect(subject.first).to eq("Title,Description,Visible ?,File,Published at,Created at") }
174
174
  it { expect(subject.last).to match(/^Locomotive,Lorem ipsum....,false,http:\/\/example.com\/sites\/[0-9a-f]+\/content_entry[0-9a-f]+\/[0-9a-f]+\/files\/5k.png,\"\",\"July 05, 2013 00:00\"$/) }
175
175
 
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Locomotive::EditableElement do
4
+
5
+ let(:attributes) { {} }
6
+ let(:element) { described_class.new(attributes) }
7
+
8
+ describe '#label' do
9
+
10
+ subject { element.label }
11
+
12
+ let(:attributes) { { slug: 'first_column', label: 'Column #1' } }
13
+
14
+ it { is_expected.to eq 'Column #1' }
15
+
16
+ describe 'if not defined, use the slug' do
17
+
18
+ let(:attributes) { { slug: 'first_column' } }
19
+
20
+ it { is_expected.to eq 'First column' }
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -39,7 +39,7 @@ describe Locomotive::Steam::Middlewares::Cache do
39
39
 
40
40
  subject { middleware.send(:cache_key, steam_env) }
41
41
 
42
- it { expect(subject).to eq 'd099597c3f59bb17f8a18443611ec654' }
42
+ it { expect(subject).to eq '2dbc65c2496339c339fe4977f8301d57' }
43
43
 
44
44
  end
45
45
 
@@ -4,14 +4,14 @@ Capybara.configure do |config|
4
4
  config.app_host = 'http://localhost:9886'
5
5
  end
6
6
 
7
- Capybara.default_wait_time = 5
8
-
9
- Capybara.javascript_driver = :poltergeist
7
+ Capybara.default_max_wait_time = 5
10
8
 
11
9
  Capybara.register_driver :poltergeist do |app|
12
10
  Capybara::Poltergeist::Driver.new(app, timeout: 60)
13
11
  end
14
12
 
13
+ Capybara.javascript_driver = :poltergeist
14
+
15
15
  # Stop endless errors like
16
16
  # ~/.rvm/gems/ruby-1.9.2-p0@global/gems/rack-1.2.1/lib/rack/utils.rb:16:
17
17
  # warning: regexp match /.../n against to UTF-8 string
@@ -18,10 +18,9 @@ module Features
18
18
  click_button 'Sign in'
19
19
  end
20
20
 
21
- def forgot_password(js = false, &block)
21
+ def forgot_password(&block)
22
22
  sign_up_with 'John Doe', 'john@doe.net', 'password'
23
- click_link 'Welcome, John Doe' if js
24
- within('.header') { click_link 'Log out' }
23
+ click_link 'Log out'
25
24
  click_link 'Forgot my password'
26
25
  fill_in 'Your email', with: 'john@doe.net'
27
26
  click_button 'Submit'
@@ -1,13 +1,14 @@
1
1
  .bootstrap-tagsinput {
2
2
  background-color: #fff;
3
+ border: 1px solid #ccc;
4
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
3
5
  display: inline-block;
4
6
  padding: 4px 6px;
5
- margin-bottom: 2px;
6
7
  color: #555;
7
8
  vertical-align: middle;
8
9
  border-radius: 4px;
9
- width: 100%;
10
- line-height: 35px;
10
+ max-width: 100%;
11
+ line-height: 22px;
11
12
  cursor: text;
12
13
  }
13
14
  .bootstrap-tagsinput input {
@@ -15,11 +16,20 @@
15
16
  box-shadow: none;
16
17
  outline: none;
17
18
  background-color: transparent;
18
- padding: 0;
19
+ padding: 0 6px;
19
20
  margin: 0;
20
- width: auto !important;
21
+ width: auto;
21
22
  max-width: inherit;
22
- font-size: 12px;
23
+ }
24
+ .bootstrap-tagsinput.form-control input::-moz-placeholder {
25
+ color: #777;
26
+ opacity: 1;
27
+ }
28
+ .bootstrap-tagsinput.form-control input:-ms-input-placeholder {
29
+ color: #777;
30
+ }
31
+ .bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
32
+ color: #777;
23
33
  }
24
34
  .bootstrap-tagsinput input:focus {
25
35
  border: none;
@@ -27,7 +37,6 @@
27
37
  }
28
38
  .bootstrap-tagsinput .tag {
29
39
  margin-right: 2px;
30
- font-size: 12px;
31
40
  color: white;
32
41
  }
33
42
  .bootstrap-tagsinput .tag [data-role="remove"] {
@@ -0,0 +1,663 @@
1
+ (function ($) {
2
+ "use strict";
3
+
4
+ var defaultOptions = {
5
+ tagClass: function(item) {
6
+ return 'label label-info';
7
+ },
8
+ itemValue: function(item) {
9
+ return item ? item.toString() : item;
10
+ },
11
+ itemText: function(item) {
12
+ return this.itemValue(item);
13
+ },
14
+ itemTitle: function(item) {
15
+ return null;
16
+ },
17
+ freeInput: true,
18
+ addOnBlur: true,
19
+ maxTags: undefined,
20
+ maxChars: undefined,
21
+ confirmKeys: [13, 44],
22
+ delimiter: ',',
23
+ delimiterRegex: null,
24
+ cancelConfirmKeysOnEmpty: false,
25
+ onTagExists: function(item, $tag) {
26
+ $tag.hide().fadeIn();
27
+ },
28
+ trimValue: false,
29
+ allowDuplicates: false
30
+ };
31
+
32
+ /**
33
+ * Constructor function
34
+ */
35
+ function TagsInput(element, options) {
36
+ this.isInit = true;
37
+ this.itemsArray = [];
38
+
39
+ this.$element = $(element);
40
+ this.$element.hide();
41
+
42
+ this.isSelect = (element.tagName === 'SELECT');
43
+ this.multiple = (this.isSelect && element.hasAttribute('multiple'));
44
+ this.objectItems = options && options.itemValue;
45
+ this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
46
+ this.inputSize = Math.max(1, this.placeholderText.length);
47
+
48
+ this.$container = $('<div class="bootstrap-tagsinput"></div>');
49
+ this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
50
+
51
+ this.$element.before(this.$container);
52
+
53
+ this.build(options);
54
+ this.isInit = false;
55
+ }
56
+
57
+ TagsInput.prototype = {
58
+ constructor: TagsInput,
59
+
60
+ /**
61
+ * Adds the given item as a new tag. Pass true to dontPushVal to prevent
62
+ * updating the elements val()
63
+ */
64
+ add: function(item, dontPushVal, options) {
65
+ var self = this;
66
+
67
+ if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
68
+ return;
69
+
70
+ // Ignore falsey values, except false
71
+ if (item !== false && !item)
72
+ return;
73
+
74
+ // Trim value
75
+ if (typeof item === "string" && self.options.trimValue) {
76
+ item = $.trim(item);
77
+ }
78
+
79
+ // Throw an error when trying to add an object while the itemValue option was not set
80
+ if (typeof item === "object" && !self.objectItems)
81
+ throw("Can't add objects when itemValue option is not set");
82
+
83
+ // Ignore strings only containg whitespace
84
+ if (item.toString().match(/^\s*$/))
85
+ return;
86
+
87
+ // If SELECT but not multiple, remove current tag
88
+ if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
89
+ self.remove(self.itemsArray[0]);
90
+
91
+ if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
92
+ var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
93
+ var items = item.split(delimiter);
94
+ if (items.length > 1) {
95
+ for (var i = 0; i < items.length; i++) {
96
+ this.add(items[i], true);
97
+ }
98
+
99
+ if (!dontPushVal)
100
+ self.pushVal();
101
+ return;
102
+ }
103
+ }
104
+
105
+ var itemValue = self.options.itemValue(item),
106
+ itemText = self.options.itemText(item),
107
+ tagClass = self.options.tagClass(item),
108
+ itemTitle = self.options.itemTitle(item);
109
+
110
+ // Ignore items allready added
111
+ var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
112
+ if (existing && !self.options.allowDuplicates) {
113
+ // Invoke onTagExists
114
+ if (self.options.onTagExists) {
115
+ var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
116
+ self.options.onTagExists(item, $existingTag);
117
+ }
118
+ return;
119
+ }
120
+
121
+ // if length greater than limit
122
+ if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
123
+ return;
124
+
125
+ // raise beforeItemAdd arg
126
+ var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
127
+ self.$element.trigger(beforeItemAddEvent);
128
+ if (beforeItemAddEvent.cancel)
129
+ return;
130
+
131
+ // register item in internal array and map
132
+ self.itemsArray.push(item);
133
+
134
+ // add a tag element
135
+
136
+ var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
137
+ $tag.data('item', item);
138
+ self.findInputWrapper().before($tag);
139
+ $tag.after(' ');
140
+
141
+ // Check to see if the tag exists in its raw or uri-encoded form
142
+ var optionExists = (
143
+ $('option[value="' + encodeURIComponent(itemValue) + '"]', self.$element).length ||
144
+ $('option[value="' + htmlEncode(itemValue) + '"]', self.$element).length
145
+ );
146
+
147
+ // add <option /> if item represents a value not present in one of the <select />'s options
148
+ if (self.isSelect && !optionExists) {
149
+ var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
150
+ $option.data('item', item);
151
+ $option.attr('value', itemValue);
152
+ self.$element.append($option);
153
+ }
154
+
155
+ if (!dontPushVal)
156
+ self.pushVal();
157
+
158
+ // Add class when reached maxTags
159
+ if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
160
+ self.$container.addClass('bootstrap-tagsinput-max');
161
+
162
+ // If using typeahead, once the tag has been added, clear the typeahead value so it does not stick around in the input.
163
+ if ($('.typeahead, .twitter-typeahead', self.$container).length) {
164
+ self.$input.typeahead('val', '');
165
+ }
166
+
167
+ if (this.isInit) {
168
+ self.$element.trigger($.Event('itemAddedOnInit', { item: item, options: options }));
169
+ } else {
170
+ self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
171
+ }
172
+ },
173
+
174
+ /**
175
+ * Removes the given item. Pass true to dontPushVal to prevent updating the
176
+ * elements val()
177
+ */
178
+ remove: function(item, dontPushVal, options) {
179
+ var self = this;
180
+
181
+ if (self.objectItems) {
182
+ if (typeof item === "object")
183
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
184
+ else
185
+ item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
186
+
187
+ item = item[item.length-1];
188
+ }
189
+
190
+ if (item) {
191
+ var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
192
+ self.$element.trigger(beforeItemRemoveEvent);
193
+ if (beforeItemRemoveEvent.cancel)
194
+ return;
195
+
196
+ $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
197
+ $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
198
+ if($.inArray(item, self.itemsArray) !== -1)
199
+ self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
200
+ }
201
+
202
+ if (!dontPushVal)
203
+ self.pushVal();
204
+
205
+ // Remove class when reached maxTags
206
+ if (self.options.maxTags > self.itemsArray.length)
207
+ self.$container.removeClass('bootstrap-tagsinput-max');
208
+
209
+ self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
210
+ },
211
+
212
+ /**
213
+ * Removes all items
214
+ */
215
+ removeAll: function() {
216
+ var self = this;
217
+
218
+ $('.tag', self.$container).remove();
219
+ $('option', self.$element).remove();
220
+
221
+ while(self.itemsArray.length > 0)
222
+ self.itemsArray.pop();
223
+
224
+ self.pushVal();
225
+ },
226
+
227
+ /**
228
+ * Refreshes the tags so they match the text/value of their corresponding
229
+ * item.
230
+ */
231
+ refresh: function() {
232
+ var self = this;
233
+ $('.tag', self.$container).each(function() {
234
+ var $tag = $(this),
235
+ item = $tag.data('item'),
236
+ itemValue = self.options.itemValue(item),
237
+ itemText = self.options.itemText(item),
238
+ tagClass = self.options.tagClass(item);
239
+
240
+ // Update tag's class and inner text
241
+ $tag.attr('class', null);
242
+ $tag.addClass('tag ' + htmlEncode(tagClass));
243
+ $tag.contents().filter(function() {
244
+ return this.nodeType == 3;
245
+ })[0].nodeValue = htmlEncode(itemText);
246
+
247
+ if (self.isSelect) {
248
+ var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
249
+ option.attr('value', itemValue);
250
+ }
251
+ });
252
+ },
253
+
254
+ /**
255
+ * Returns the items added as tags
256
+ */
257
+ items: function() {
258
+ return this.itemsArray;
259
+ },
260
+
261
+ /**
262
+ * Assembly value by retrieving the value of each item, and set it on the
263
+ * element.
264
+ */
265
+ pushVal: function() {
266
+ var self = this,
267
+ val = $.map(self.items(), function(item) {
268
+ return self.options.itemValue(item).toString();
269
+ });
270
+
271
+ self.$element.val(val, true).trigger('change');
272
+ },
273
+
274
+ /**
275
+ * Initializes the tags input behaviour on the element
276
+ */
277
+ build: function(options) {
278
+ var self = this;
279
+
280
+ self.options = $.extend({}, defaultOptions, options);
281
+ // When itemValue is set, freeInput should always be false
282
+ if (self.objectItems)
283
+ self.options.freeInput = false;
284
+
285
+ makeOptionItemFunction(self.options, 'itemValue');
286
+ makeOptionItemFunction(self.options, 'itemText');
287
+ makeOptionFunction(self.options, 'tagClass');
288
+
289
+ // Typeahead Bootstrap version 2.3.2
290
+ if (self.options.typeahead) {
291
+ var typeahead = self.options.typeahead || {};
292
+
293
+ makeOptionFunction(typeahead, 'source');
294
+
295
+ self.$input.typeahead($.extend({}, typeahead, {
296
+ source: function (query, process) {
297
+ function processItems(items) {
298
+ var texts = [];
299
+
300
+ for (var i = 0; i < items.length; i++) {
301
+ var text = self.options.itemText(items[i]);
302
+ map[text] = items[i];
303
+ texts.push(text);
304
+ }
305
+ process(texts);
306
+ }
307
+
308
+ this.map = {};
309
+ var map = this.map,
310
+ data = typeahead.source(query);
311
+
312
+ if ($.isFunction(data.success)) {
313
+ // support for Angular callbacks
314
+ data.success(processItems);
315
+ } else if ($.isFunction(data.then)) {
316
+ // support for Angular promises
317
+ data.then(processItems);
318
+ } else {
319
+ // support for functions and jquery promises
320
+ $.when(data)
321
+ .then(processItems);
322
+ }
323
+ },
324
+ updater: function (text) {
325
+ self.add(this.map[text]);
326
+ return this.map[text];
327
+ },
328
+ matcher: function (text) {
329
+ return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
330
+ },
331
+ sorter: function (texts) {
332
+ return texts.sort();
333
+ },
334
+ highlighter: function (text) {
335
+ var regex = new RegExp( '(' + this.query + ')', 'gi' );
336
+ return text.replace( regex, "<strong>$1</strong>" );
337
+ }
338
+ }));
339
+ }
340
+
341
+ // typeahead.js
342
+ if (self.options.typeaheadjs) {
343
+ var typeaheadConfig = null;
344
+ var typeaheadDatasets = {};
345
+
346
+ // Determine if main configurations were passed or simply a dataset
347
+ var typeaheadjs = self.options.typeaheadjs;
348
+ if ($.isArray(typeaheadjs)) {
349
+ typeaheadConfig = typeaheadjs[0];
350
+ typeaheadDatasets = typeaheadjs[1];
351
+ } else {
352
+ typeaheadDatasets = typeaheadjs;
353
+ }
354
+
355
+ self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
356
+ if (typeaheadDatasets.valueKey)
357
+ self.add(datum[typeaheadDatasets.valueKey]);
358
+ else
359
+ self.add(datum);
360
+ self.$input.typeahead('val', '');
361
+ }, self));
362
+ }
363
+
364
+ self.$container.on('click', $.proxy(function(event) {
365
+ if (! self.$element.attr('disabled')) {
366
+ self.$input.removeAttr('disabled');
367
+ }
368
+ self.$input.focus();
369
+ }, self));
370
+
371
+ if (self.options.addOnBlur && self.options.freeInput) {
372
+ self.$input.on('focusout', $.proxy(function(event) {
373
+ // HACK: only process on focusout when no typeahead opened, to
374
+ // avoid adding the typeahead text as tag
375
+ if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
376
+ self.add(self.$input.val());
377
+ self.$input.val('');
378
+ }
379
+ }, self));
380
+ }
381
+
382
+
383
+ self.$container.on('keydown', 'input', $.proxy(function(event) {
384
+ var $input = $(event.target),
385
+ $inputWrapper = self.findInputWrapper();
386
+
387
+ if (self.$element.attr('disabled')) {
388
+ self.$input.attr('disabled', 'disabled');
389
+ return;
390
+ }
391
+
392
+ switch (event.which) {
393
+ // BACKSPACE
394
+ case 8:
395
+ if (doGetCaretPosition($input[0]) === 0) {
396
+ var prev = $inputWrapper.prev();
397
+ if (prev.length) {
398
+ self.remove(prev.data('item'));
399
+ }
400
+ }
401
+ break;
402
+
403
+ // DELETE
404
+ case 46:
405
+ if (doGetCaretPosition($input[0]) === 0) {
406
+ var next = $inputWrapper.next();
407
+ if (next.length) {
408
+ self.remove(next.data('item'));
409
+ }
410
+ }
411
+ break;
412
+
413
+ // LEFT ARROW
414
+ case 37:
415
+ // Try to move the input before the previous tag
416
+ var $prevTag = $inputWrapper.prev();
417
+ if ($input.val().length === 0 && $prevTag[0]) {
418
+ $prevTag.before($inputWrapper);
419
+ $input.focus();
420
+ }
421
+ break;
422
+ // RIGHT ARROW
423
+ case 39:
424
+ // Try to move the input after the next tag
425
+ var $nextTag = $inputWrapper.next();
426
+ if ($input.val().length === 0 && $nextTag[0]) {
427
+ $nextTag.after($inputWrapper);
428
+ $input.focus();
429
+ }
430
+ break;
431
+ default:
432
+ // ignore
433
+ }
434
+
435
+ // Reset internal input's size
436
+ var textLength = $input.val().length,
437
+ wordSpace = Math.ceil(textLength / 5),
438
+ size = textLength + wordSpace + 1;
439
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
440
+ }, self));
441
+
442
+ self.$container.on('keypress', 'input', $.proxy(function(event) {
443
+ var $input = $(event.target);
444
+
445
+ if (self.$element.attr('disabled')) {
446
+ self.$input.attr('disabled', 'disabled');
447
+ return;
448
+ }
449
+
450
+ var text = $input.val(),
451
+ maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
452
+ if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
453
+ // Only attempt to add a tag if there is data in the field
454
+ if (text.length !== 0) {
455
+ self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
456
+ $input.val('');
457
+ }
458
+
459
+ // If the field is empty, let the event triggered fire as usual
460
+ if (self.options.cancelConfirmKeysOnEmpty === false) {
461
+ event.preventDefault();
462
+ }
463
+ }
464
+
465
+ // Reset internal input's size
466
+ var textLength = $input.val().length,
467
+ wordSpace = Math.ceil(textLength / 5),
468
+ size = textLength + wordSpace + 1;
469
+ $input.attr('size', Math.max(this.inputSize, $input.val().length));
470
+ }, self));
471
+
472
+ // Remove icon clicked
473
+ self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
474
+ if (self.$element.attr('disabled')) {
475
+ return;
476
+ }
477
+ self.remove($(event.target).closest('.tag').data('item'));
478
+ }, self));
479
+
480
+ // Only add existing value as tags when using strings as tags
481
+ if (self.options.itemValue === defaultOptions.itemValue) {
482
+ if (self.$element[0].tagName === 'INPUT') {
483
+ self.add(self.$element.val());
484
+ } else {
485
+ $('option', self.$element).each(function() {
486
+ self.add($(this).attr('value'), true);
487
+ });
488
+ }
489
+ }
490
+ },
491
+
492
+ /**
493
+ * Removes all tagsinput behaviour and unregsiter all event handlers
494
+ */
495
+ destroy: function() {
496
+ var self = this;
497
+
498
+ // Unbind events
499
+ self.$container.off('keypress', 'input');
500
+ self.$container.off('click', '[role=remove]');
501
+
502
+ self.$container.remove();
503
+ self.$element.removeData('tagsinput');
504
+ self.$element.show();
505
+ },
506
+
507
+ /**
508
+ * Sets focus on the tagsinput
509
+ */
510
+ focus: function() {
511
+ this.$input.focus();
512
+ },
513
+
514
+ /**
515
+ * Returns the internal input element
516
+ */
517
+ input: function() {
518
+ return this.$input;
519
+ },
520
+
521
+ /**
522
+ * Returns the element which is wrapped around the internal input. This
523
+ * is normally the $container, but typeahead.js moves the $input element.
524
+ */
525
+ findInputWrapper: function() {
526
+ var elt = this.$input[0],
527
+ container = this.$container[0];
528
+ while(elt && elt.parentNode !== container)
529
+ elt = elt.parentNode;
530
+
531
+ return $(elt);
532
+ }
533
+ };
534
+
535
+ /**
536
+ * Register JQuery plugin
537
+ */
538
+ $.fn.tagsinput = function(arg1, arg2, arg3) {
539
+ var results = [];
540
+
541
+ this.each(function() {
542
+ var tagsinput = $(this).data('tagsinput');
543
+ // Initialize a new tags input
544
+ if (!tagsinput) {
545
+ tagsinput = new TagsInput(this, arg1);
546
+ $(this).data('tagsinput', tagsinput);
547
+ results.push(tagsinput);
548
+
549
+ if (this.tagName === 'SELECT') {
550
+ $('option', $(this)).attr('selected', 'selected');
551
+ }
552
+
553
+ // Init tags from $(this).val()
554
+ $(this).val($(this).val());
555
+ } else if (!arg1 && !arg2) {
556
+ // tagsinput already exists
557
+ // no function, trying to init
558
+ results.push(tagsinput);
559
+ } else if(tagsinput[arg1] !== undefined) {
560
+ // Invoke function on existing tags input
561
+ if(tagsinput[arg1].length === 3 && arg3 !== undefined){
562
+ var retVal = tagsinput[arg1](arg2, null, arg3);
563
+ }else{
564
+ var retVal = tagsinput[arg1](arg2);
565
+ }
566
+ if (retVal !== undefined)
567
+ results.push(retVal);
568
+ }
569
+ });
570
+
571
+ if ( typeof arg1 == 'string') {
572
+ // Return the results from the invoked function calls
573
+ return results.length > 1 ? results : results[0];
574
+ } else {
575
+ return results;
576
+ }
577
+ };
578
+
579
+ $.fn.tagsinput.Constructor = TagsInput;
580
+
581
+ /**
582
+ * Most options support both a string or number as well as a function as
583
+ * option value. This function makes sure that the option with the given
584
+ * key in the given options is wrapped in a function
585
+ */
586
+ function makeOptionItemFunction(options, key) {
587
+ if (typeof options[key] !== 'function') {
588
+ var propertyName = options[key];
589
+ options[key] = function(item) { return item[propertyName]; };
590
+ }
591
+ }
592
+ function makeOptionFunction(options, key) {
593
+ if (typeof options[key] !== 'function') {
594
+ var value = options[key];
595
+ options[key] = function() { return value; };
596
+ }
597
+ }
598
+ /**
599
+ * HtmlEncodes the given value
600
+ */
601
+ var htmlEncodeContainer = $('<div />');
602
+ function htmlEncode(value) {
603
+ if (value) {
604
+ return htmlEncodeContainer.text(value).html();
605
+ } else {
606
+ return '';
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Returns the position of the caret in the given input field
612
+ * http://flightschool.acylt.com/devnotes/caret-position-woes/
613
+ */
614
+ function doGetCaretPosition(oField) {
615
+ var iCaretPos = 0;
616
+ if (document.selection) {
617
+ oField.focus ();
618
+ var oSel = document.selection.createRange();
619
+ oSel.moveStart ('character', -oField.value.length);
620
+ iCaretPos = oSel.text.length;
621
+ } else if (oField.selectionStart || oField.selectionStart == '0') {
622
+ iCaretPos = oField.selectionStart;
623
+ }
624
+ return (iCaretPos);
625
+ }
626
+
627
+ /**
628
+ * Returns boolean indicates whether user has pressed an expected key combination.
629
+ * @param object keyPressEvent: JavaScript event object, refer
630
+ * http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
631
+ * @param object lookupList: expected key combinations, as in:
632
+ * [13, {which: 188, shiftKey: true}]
633
+ */
634
+ function keyCombinationInList(keyPressEvent, lookupList) {
635
+ var found = false;
636
+ $.each(lookupList, function (index, keyCombination) {
637
+ if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
638
+ found = true;
639
+ return false;
640
+ }
641
+
642
+ if (keyPressEvent.which === keyCombination.which) {
643
+ var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
644
+ shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
645
+ ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
646
+ if (alt && shift && ctrl) {
647
+ found = true;
648
+ return false;
649
+ }
650
+ }
651
+ });
652
+
653
+ return found;
654
+ }
655
+
656
+ /**
657
+ * Initialize tagsinput behaviour on inputs and selects which have
658
+ * data-role=tagsinput
659
+ */
660
+ $(function() {
661
+ $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
662
+ });
663
+ })(window.jQuery);