spree_core 1.3.0.rc1 → 1.3.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/app/assets/javascripts/admin/admin.js.erb +28 -2
  2. data/app/assets/javascripts/admin/checkouts/edit.js +52 -96
  3. data/app/assets/javascripts/admin/handlebar_extensions.js +9 -0
  4. data/app/assets/javascripts/admin/spree-select2.js.erb +17 -19
  5. data/app/assets/javascripts/admin/spree_core.js +0 -1
  6. data/app/assets/javascripts/admin/states.js +9 -0
  7. data/app/assets/javascripts/admin/taxon_autocomplete.js.erb +1 -1
  8. data/app/assets/javascripts/admin/variant_autocomplete.js.erb +2 -6
  9. data/app/assets/javascripts/store/checkout.js.coffee +4 -4
  10. data/app/assets/stylesheets/store/screen.css.scss +8 -0
  11. data/app/controllers/spree/admin/option_values_controller.rb +11 -0
  12. data/app/controllers/spree/admin/product_properties_controller.rb +12 -0
  13. data/app/controllers/spree/admin/products_controller.rb +9 -0
  14. data/app/helpers/spree/admin/base_helper.rb +2 -8
  15. data/app/models/spree/app_configuration.rb +1 -0
  16. data/app/models/spree/calculator/per_item.rb +3 -1
  17. data/app/models/spree/line_item.rb +2 -2
  18. data/app/models/spree/order/checkout.rb +4 -1
  19. data/app/models/spree/order.rb +5 -1
  20. data/app/models/spree/payment/processing.rb +1 -1
  21. data/app/models/spree/preferences/preferable_class_methods.rb +6 -9
  22. data/app/models/spree/preferences/store.rb +22 -11
  23. data/app/models/spree/product/scopes.rb +5 -1
  24. data/app/models/spree/shipment.rb +7 -4
  25. data/app/models/spree/shipping_method.rb +4 -0
  26. data/app/views/spree/admin/orders/customer_details/_autocomplete.js.erb +19 -0
  27. data/app/views/spree/admin/orders/customer_details/edit.html.erb +4 -3
  28. data/app/views/spree/admin/orders/edit.html.erb +1 -1
  29. data/app/views/spree/admin/products/search.rabl +6 -0
  30. data/app/views/spree/admin/search/users.rabl +23 -25
  31. data/app/views/spree/admin/shared/_head.html.erb +1 -1
  32. data/app/views/spree/admin/shared/_routes.html.erb +3 -2
  33. data/app/views/spree/admin/shared/_translations.html.erb +3 -0
  34. data/app/views/spree/admin/shipping_methods/_form.html.erb +1 -1
  35. data/app/views/spree/admin/variants/_autocomplete.js.erb +2 -2
  36. data/app/views/spree/admin/variants/search.rabl +8 -10
  37. data/app/views/spree/checkout/payment/_gateway.html.erb +2 -2
  38. data/app/views/spree/order_mailer/cancel_email.text.erb +2 -2
  39. data/app/views/spree/order_mailer/confirm_email.text.erb +2 -2
  40. data/app/views/spree/orders/_line_item.html.erb +1 -1
  41. data/app/views/spree/products/_cart_form.html.erb +6 -2
  42. data/app/views/spree/shared/_google_analytics.html.erb +8 -8
  43. data/app/views/spree/shared/_order_details.html.erb +3 -1
  44. data/config/locales/en.yml +9 -7
  45. data/config/routes.rb +9 -0
  46. data/db/migrate/20121126040517_add_last_ip_to_spree_orders.rb +5 -0
  47. data/lib/spree/core/calculated_adjustments.rb +2 -1
  48. data/lib/spree/core/controller_helpers/common.rb +5 -0
  49. data/lib/spree/core/controller_helpers/order.rb +1 -0
  50. data/lib/spree/core/search/base.rb +5 -1
  51. data/lib/spree/core/testing_support/capybara_ext.rb +79 -0
  52. data/lib/spree/core/version.rb +1 -1
  53. metadata +13 -6
  54. data/vendor/assets/javascripts/jquery.tokeninput.js +0 -860
@@ -9,12 +9,12 @@
9
9
  <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.variant.display_amount %> = <%= item.display_amount %>
10
10
  <% end %>
11
11
  ============================================================
12
- <%= t('order_mailer.confirm_email.subtotal') %>: <%= @order.display_item_total %>
12
+ <%= t('order_mailer.confirm_email.subtotal', :subtotal => @order.display_item_total) %>
13
13
 
14
14
  <% @order.adjustments.eligible.each do |adjustment| %>
15
15
  <%= raw(adjustment.label) %> <%= adjustment.display_amount %>
16
16
  <% end %>
17
17
 
18
- <%= t('order_mailer.confirm_email.total') %>: <%= @order.display_total %>
18
+ <%= t('order_mailer.confirm_email.total', :total => @order.display_total) %>
19
19
 
20
20
  <%= t('order_mailer.confirm_email.thanks') %>
@@ -11,7 +11,7 @@
11
11
  <%= variant.options_text %>
12
12
  <% if @order.insufficient_stock_lines.include? line_item %>
13
13
  <span class="out-of-stock">
14
- <%= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) %><br />
14
+ <%= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) %>&nbsp;&nbsp;<br />
15
15
  </span>
16
16
  <% end %>
17
17
  <%= line_item_description(variant) %>
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  <% end%>
28
28
 
29
- <% if @product.price %>
29
+ <% if @product.price_in(current_currency) and !@product.price.nil? %>
30
30
  <div data-hook="product_price" class="columns five <% if !@product.has_variants? %> alpha <% else %> omega <% end %>">
31
31
 
32
32
  <div id="product-price">
@@ -45,8 +45,12 @@
45
45
  <%= content_tag('strong', t(:out_of_stock)) %>
46
46
  <% end %>
47
47
  </div>
48
-
49
48
  </div>
49
+ <% else %>
50
+ <div id="product-price">
51
+ <br>
52
+ <div><span class="price selling" itemprop="price"><%= t('product_not_available_in_this_currency') %></span></div>
53
+ </div>
50
54
  <% end %>
51
55
 
52
56
  </div>
@@ -5,23 +5,23 @@
5
5
  _gaq.push(['_setAccount', '<%= tracker.analytics_id %>']);
6
6
  _gaq.push(['_trackPageview']);
7
7
 
8
- <% if flash[:commerce_tracking] %>
8
+ <% if flash[:commerce_tracking] && @order.present? %>
9
9
  <%# more info: https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiEcommerce %>
10
10
  _gaq.push(['_addTrans',
11
- "<%= @order.number %>",
11
+ "<%= j @order.number %>",
12
12
  "",
13
13
  "<%= @order.total %>",
14
14
  "<%= @order.adjustments.tax.sum(:amount) %>",
15
15
  "<%= @order.adjustments.shipping.sum(:amount) %>",
16
- "<%= @order.bill_address.city %>",
17
- "<%= @order.bill_address.state_text %>",
18
- "<%= @order.bill_address.country.name %>"
16
+ "<%= j @order.bill_address.city %>",
17
+ "<%= j @order.bill_address.state_text %>",
18
+ "<%= j @order.bill_address.country.name %>"
19
19
  ]);
20
20
  <% @order.line_items.each do |line_item| %>
21
21
  _gaq.push(['_addItem',
22
- "<%= @order.number %>",
23
- "<%= line_item.variant.sku %>",
24
- "<%= line_item.variant.product.name %>",
22
+ "<%= j @order.number %>",
23
+ "<%= j line_item.variant.sku %>",
24
+ "<%= j line_item.variant.product.name %>",
25
25
  "",
26
26
  "<%= line_item.price %>",
27
27
  "<%= line_item.quantity %>"
@@ -32,7 +32,9 @@
32
32
  <%= content_tag(:span, order.payment.payment_method.name) if order.payment %>
33
33
  <% else %>
34
34
  <span class="cc-type">
35
- <%= image_tag "credit_cards/icons/#{order.credit_cards.first.cc_type}.png" %>
35
+ <% unless (cc_type = order.credit_cards.first.cc_type).blank? %>
36
+ <%= image_tag "credit_cards/icons/#{order.credit_cards.first.cc_type}.png" %>
37
+ <% end %>
36
38
  <%= t(:ending_in)%> <%= order.credit_cards.first.last_digits %>
37
39
  </span>
38
40
  <br />
@@ -321,6 +321,7 @@ en:
321
321
  charges: Charges
322
322
  checkout: Checkout
323
323
  cheque: Cheque
324
+ choose_a_customer: "Choose a customer"
324
325
  city: City
325
326
  clone: Clone
326
327
  code: Code
@@ -642,16 +643,16 @@ en:
642
643
  dear_customer: "Dear Customer,"
643
644
  instructions: "Please review and retain the following order information for your records."
644
645
  order_summary: "Order Summary"
645
- subtotal: "Subtotal:"
646
- total: "Order Total:"
646
+ subtotal: "Subtotal: %{subtotal}"
647
+ total: "Order Total: %{total}"
647
648
  thanks: "Thank you for your business."
648
649
  cancel_email:
649
650
  subject: "Cancellation of Order"
650
651
  dear_customer: "Dear Customer,"
651
652
  instructions: "Your order has been CANCELED. Please retain this cancellation information for your records."
652
653
  order_summary_canceled: "Order Summary [CANCELED]"
653
- subtotal: "Subtotal:"
654
- total: "Order Total:"
654
+ subtotal: "Subtotal: %{subtotal}"
655
+ total: "Order Total: %{total}"
655
656
  order_not_in_system: That order number is not valid on this site.
656
657
  order_number: Order
657
658
  order_operation_authorize: Authorize
@@ -858,6 +859,7 @@ en:
858
859
  sentence: with property <em>%s</em> and value <em>%s</em>
859
860
  products: Products
860
861
  products_with_zero_inventory_display: "Products with a zero inventory will %{not} be displayed"
862
+ product_not_available_in_this_currency: "This product is not available in the selected currency."
861
863
  properties: Properties
862
864
  property: Property
863
865
  prototype: Prototype
@@ -1045,6 +1047,7 @@ en:
1045
1047
  tax_type: "Tax Type"
1046
1048
  taxon: Taxon
1047
1049
  taxon_edit: Edit Taxon
1050
+ taxon_placeholder: "Add a Taxon"
1048
1051
  taxonomy: Taxonomy
1049
1052
  taxonomies: Taxonomies
1050
1053
  taxonomies_setting_description: "Create and manage taxonomies."
@@ -1061,7 +1064,6 @@ en:
1061
1064
  test_mode: Test Mode
1062
1065
  thank_you_for_your_order: "Thank you for your business. Please print out a copy of this confirmation page for your records."
1063
1066
  there_were_problems_with_the_following_fields: "There were problems with the following fields"
1064
- this_file_language: "English (US)"
1065
1067
  thumbnail: "Thumbnail"
1066
1068
  to_add_variants_you_must_first_define: "To add variants, you must first define"
1067
1069
  to_state: "To State"
@@ -1097,12 +1099,12 @@ en:
1097
1099
  users: Users
1098
1100
  validate_on_profile_create: Validate on profile create
1099
1101
  validation:
1100
- cannot_be_greater_than_available_stock: "cannot be greater than available stock."
1101
1102
  cannot_be_less_than_shipped_units: "cannot be less than the number of shipped units."
1102
- cannot_destory_line_item_as_inventory_units_have_shipped: "Cannot destory line item as some inventory units have shipped."
1103
+ cannot_destroy_line_item_as_inventory_units_have_shipped: "Cannot destroy line item as some inventory units have shipped."
1103
1104
  is_too_large: "is too large -- stock on hand cannot cover requested quantity!"
1104
1105
  must_be_int: "must be an integer"
1105
1106
  must_be_non_negative: "must be a non-negative value"
1107
+ exceeds_available_stock: "exceeds available stock. Please ensure line items have a valid quantity."
1106
1108
  value: Value
1107
1109
  variant: Variant
1108
1110
  variants: Variants
data/config/routes.rb CHANGED
@@ -64,7 +64,12 @@ Spree::Core::Engine.routes.draw do
64
64
  end
65
65
  resources :states
66
66
  resources :tax_categories
67
+
67
68
  resources :products do
69
+ collection do
70
+ get :search
71
+ end
72
+
68
73
  resources :product_properties
69
74
  resources :images do
70
75
  collection do
@@ -90,12 +95,16 @@ Spree::Core::Engine.routes.draw do
90
95
  end
91
96
  end
92
97
 
98
+ delete '/option_values/:id', :to => "option_values#destroy", :as => :option_value
99
+
93
100
  resources :properties do
94
101
  collection do
95
102
  get :filtered
96
103
  end
97
104
  end
98
105
 
106
+ delete '/product_properties/:id', :to => "product_properties#destroy", :as => :product_property
107
+
99
108
  resources :prototypes do
100
109
  member do
101
110
  get :select
@@ -0,0 +1,5 @@
1
+ class AddLastIpToSpreeOrders < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_orders, :last_ip_address, :string
4
+ end
5
+ end
@@ -33,7 +33,8 @@ module Spree
33
33
  :source => calculable,
34
34
  :originator => self,
35
35
  :label => label,
36
- :mandatory => mandatory}, :without_protection => true)
36
+ :mandatory => mandatory,
37
+ :locked => true }, :without_protection => true)
37
38
  end
38
39
 
39
40
  # Updates the amount of the adjustment using our Calculator and calling the +compute+ method with the +calculable+
@@ -66,6 +66,11 @@ module Spree
66
66
  type.all { render :status => :not_found, :nothing => true }
67
67
  end
68
68
  end
69
+
70
+ def ip_address
71
+ request.env['HTTP_X_REAL_IP'] || request.env['REMOTE_ADDR']
72
+ end
73
+
69
74
  private
70
75
 
71
76
  def set_user_language
@@ -24,6 +24,7 @@ module Spree
24
24
  return @current_order if @current_order
25
25
  if session[:order_id]
26
26
  current_order = Spree::Order.find_by_id_and_currency(session[:order_id], current_currency, :include => :adjustments)
27
+ current_order.last_ip_address = ip_address
27
28
  @current_order = current_order unless current_order.try(:completed?)
28
29
  end
29
30
  if create_order_if_necessary and (@current_order.nil? or @current_order.completed?)
@@ -16,7 +16,11 @@ module Spree
16
16
  @products_scope = get_base_scope
17
17
  curr_page = page || 1
18
18
 
19
- @products = @products_scope.includes([:master => :prices]).where("spree_prices.amount IS NOT NULL").where("spree_prices.currency" => current_currency).page(curr_page).per(per_page)
19
+ @products = @products_scope.includes([:master => :prices])
20
+ unless Spree::Config.show_products_without_price
21
+ @products = @products.where("spree_prices.amount IS NOT NULL").where("spree_prices.currency" => current_currency)
22
+ end
23
+ @products = @products.page(curr_page).per(per_page)
20
24
  end
21
25
 
22
26
  def method_missing(name)
@@ -0,0 +1,79 @@
1
+ module CapybaraExt
2
+ def page!
3
+ save_and_open_page
4
+ end
5
+
6
+ def click_icon(type)
7
+ find(".icon-#{type}").click
8
+ end
9
+
10
+ def eventually_fill_in(field, options={})
11
+ Capybara.wait_until do
12
+ find_field field
13
+ end
14
+ fill_in field, options
15
+ end
16
+
17
+ def within_row(num, &block)
18
+ within("table.index tbody tr:nth-child(#{num})", &block)
19
+ end
20
+
21
+ def column_text(num)
22
+ find("td:nth-child(#{num})").text
23
+ end
24
+
25
+ def set_select2_field(field, value)
26
+ page.execute_script %Q{$('#{field}').select2('val', '#{value}')}
27
+ end
28
+
29
+ def select2_search(value, options)
30
+ id = find_label_by_text(options[:from])
31
+
32
+ select2_id = "#s2id_#{id}"
33
+ find(select2_id).find(".select2-choices").click
34
+ page.execute_script "$('#{select2_id} input.select2-input').val('#{value}').trigger('keyup-change');"
35
+ select_select2_result(value)
36
+ end
37
+
38
+ def select2(value, options)
39
+ id = find_label_by_text(options[:from])
40
+
41
+ # generate select2 id
42
+ select2_id = "#s2id_#{id}"
43
+
44
+ # find select2 element and click it
45
+ find("#{select2_id}").find('a').click
46
+ select_select2_result(value)
47
+ end
48
+
49
+ def select_select2_result(value)
50
+ find(:xpath, %Q{//div[@class="select2-result-label" and text()="#{value}"]}).click
51
+ end
52
+
53
+ def find_label_by_text(text)
54
+ label = find_label(text)
55
+ counter = 0
56
+
57
+ # Because JavaScript testing is prone to errors...
58
+ while label.nil? && counter < 10
59
+ sleep(1)
60
+ counter += 1
61
+ label = find_label(text)
62
+ end
63
+
64
+ if label.nil?
65
+ raise "Could not find label by text #{text}"
66
+ end
67
+
68
+ label ? label['for'] : text
69
+ end
70
+
71
+ def find_label(text)
72
+ first(:xpath, "//label[text()='#{text}']")
73
+ end
74
+
75
+ end
76
+
77
+ RSpec.configure do |c|
78
+ c.include CapybaraExt
79
+ end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
2
  def self.version
3
- "1.3.0.rc1"
3
+ "1.3.0.rc2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0.rc1
4
+ version: 1.3.0.rc2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-04 00:00:00.000000000 Z
12
+ date: 2012-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: acts_as_list
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - '='
36
36
  - !ruby/object:Gem::Version
37
- version: 2.1.4
37
+ version: 2.1.5
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - '='
44
44
  - !ruby/object:Gem::Version
45
- version: 2.1.4
45
+ version: 2.1.5
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: jquery-rails
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -349,6 +349,7 @@ files:
349
349
  - app/assets/javascripts/admin/calculator.js
350
350
  - app/assets/javascripts/admin/checkouts/edit.js
351
351
  - app/assets/javascripts/admin/gateway.js
352
+ - app/assets/javascripts/admin/handlebar_extensions.js
352
353
  - app/assets/javascripts/admin/image_settings.js.erb
353
354
  - app/assets/javascripts/admin/images/index.js.coffee
354
355
  - app/assets/javascripts/admin/images/new.js.coffee
@@ -360,6 +361,7 @@ files:
360
361
  - app/assets/javascripts/admin/shipping_methods.js.coffee
361
362
  - app/assets/javascripts/admin/spree-select2.js.erb
362
363
  - app/assets/javascripts/admin/spree_core.js
364
+ - app/assets/javascripts/admin/states.js
363
365
  - app/assets/javascripts/admin/taxon_autocomplete.js.erb
364
366
  - app/assets/javascripts/admin/taxonomy.js
365
367
  - app/assets/javascripts/admin/underscore-min.js
@@ -419,6 +421,7 @@ files:
419
421
  - app/controllers/spree/admin/line_items_controller.rb
420
422
  - app/controllers/spree/admin/mail_methods_controller.rb
421
423
  - app/controllers/spree/admin/option_types_controller.rb
424
+ - app/controllers/spree/admin/option_values_controller.rb
422
425
  - app/controllers/spree/admin/orders/customer_details_controller.rb
423
426
  - app/controllers/spree/admin/orders_controller.rb
424
427
  - app/controllers/spree/admin/overview_controller.rb
@@ -573,6 +576,7 @@ files:
573
576
  - app/views/spree/admin/orders/_add_product.html.erb
574
577
  - app/views/spree/admin/orders/_form.html.erb
575
578
  - app/views/spree/admin/orders/_line_item.html.erb
579
+ - app/views/spree/admin/orders/customer_details/_autocomplete.js.erb
576
580
  - app/views/spree/admin/orders/customer_details/_form.html.erb
577
581
  - app/views/spree/admin/orders/customer_details/edit.html.erb
578
582
  - app/views/spree/admin/orders/edit.html.erb
@@ -602,6 +606,7 @@ files:
602
606
  - app/views/spree/admin/products/index.html.erb
603
607
  - app/views/spree/admin/products/new.html.erb
604
608
  - app/views/spree/admin/products/new.js.erb
609
+ - app/views/spree/admin/products/search.rabl
605
610
  - app/views/spree/admin/properties/_form.html.erb
606
611
  - app/views/spree/admin/properties/edit.html.erb
607
612
  - app/views/spree/admin/properties/filtered.html.erb
@@ -801,6 +806,7 @@ files:
801
806
  - lib/spree/core/ssl_requirement.rb
802
807
  - lib/spree/core/store_helpers.rb
803
808
  - lib/spree/core/testing_support/authorization_helpers.rb
809
+ - lib/spree/core/testing_support/capybara_ext.rb
804
810
  - lib/spree/core/testing_support/common_rake.rb
805
811
  - lib/spree/core/testing_support/controller_requests.rb
806
812
  - lib/spree/core/testing_support/factories/activator_factory.rb
@@ -874,6 +880,7 @@ files:
874
880
  - db/migrate/20121109173623_add_cost_currency_to_variants.rb
875
881
  - db/migrate/20121111231553_remove_display_on_from_payment_methods.rb
876
882
  - db/migrate/20121124203911_add_position_to_taxonomies.rb
883
+ - db/migrate/20121126040517_add_last_ip_to_spree_orders.rb
877
884
  - db/seeds.rb
878
885
  - vendor/assets/fonts/fontawesome-webfont.eot
879
886
  - vendor/assets/fonts/fontawesome-webfont.svg
@@ -1164,7 +1171,6 @@ files:
1164
1171
  - vendor/assets/javascripts/jquery.jstree/themes/apple/style.css
1165
1172
  - vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif
1166
1173
  - vendor/assets/javascripts/jquery.powertip.js
1167
- - vendor/assets/javascripts/jquery.tokeninput.js
1168
1174
  - vendor/assets/javascripts/jquery.validate/additional-methods.min.js
1169
1175
  - vendor/assets/javascripts/jquery.validate/jquery.validate.min.js
1170
1176
  - vendor/assets/javascripts/jquery.validate/localization/messages_ar.js
@@ -1219,7 +1225,8 @@ files:
1219
1225
  - vendor/assets/stylesheets/responsive-tables.css
1220
1226
  - vendor/assets/stylesheets/skeleton.css
1221
1227
  homepage: http://spreecommerce.com
1222
- licenses: []
1228
+ licenses:
1229
+ - BSD-3
1223
1230
  post_install_message:
1224
1231
  rdoc_options: []
1225
1232
  require_paths:
@@ -1,860 +0,0 @@
1
- /*
2
- * jQuery Plugin: Tokenizing Autocomplete Text Entry
3
- * Version 1.6.0
4
- *
5
- * Copyright (c) 2009 James Smith (http://loopj.com)
6
- * Licensed jointly under the GPL and MIT licenses,
7
- * choose which one suits your project best!
8
- *
9
- */
10
-
11
- (function ($) {
12
- // Default settings
13
- var DEFAULT_SETTINGS = {
14
- // Search settings
15
- method: "GET",
16
- contentType: "json",
17
- queryParam: "q",
18
- searchDelay: 300,
19
- minChars: 1,
20
- propertyToSearch: "name",
21
- jsonContainer: null,
22
-
23
- // Display settings
24
- hintText: "Type in a search term",
25
- noResultsText: "No results",
26
- searchingText: "Searching...",
27
- deleteText: "&times;",
28
- animateDropdown: true,
29
-
30
- // Tokenization settings
31
- tokenLimit: null,
32
- tokenDelimiter: ",",
33
- preventDuplicates: false,
34
-
35
- // Output settings
36
- tokenValue: "id",
37
-
38
- // Prepopulation settings
39
- prePopulate: null,
40
- processPrePopulate: false,
41
-
42
- // Manipulation settings
43
- idPrefix: "token-input-",
44
-
45
- // Formatters
46
- resultsFormatter: function(item){ return "<li>" + item[this.propertyToSearch]+ "</li>" },
47
- tokenFormatter: function(item) { return "<li><p>" + item[this.propertyToSearch] + "</p></li>" },
48
-
49
- // Callbacks
50
- onResult: null,
51
- onAdd: null,
52
- onDelete: null,
53
- onReady: null
54
- };
55
-
56
- // Default classes to use when theming
57
- var DEFAULT_CLASSES = {
58
- tokenList: "token-input-list",
59
- token: "token-input-token",
60
- tokenDelete: "token-input-delete-token",
61
- selectedToken: "token-input-selected-token",
62
- highlightedToken: "token-input-highlighted-token",
63
- dropdown: "token-input-dropdown",
64
- dropdownItem: "token-input-dropdown-item",
65
- dropdownItem2: "token-input-dropdown-item2",
66
- selectedDropdownItem: "token-input-selected-dropdown-item",
67
- inputToken: "token-input-input-token"
68
- };
69
-
70
- // Input box position "enum"
71
- var POSITION = {
72
- BEFORE: 0,
73
- AFTER: 1,
74
- END: 2
75
- };
76
-
77
- // Keys "enum"
78
- var KEY = {
79
- BACKSPACE: 8,
80
- TAB: 9,
81
- ENTER: 13,
82
- ESCAPE: 27,
83
- SPACE: 32,
84
- PAGE_UP: 33,
85
- PAGE_DOWN: 34,
86
- END: 35,
87
- HOME: 36,
88
- LEFT: 37,
89
- UP: 38,
90
- RIGHT: 39,
91
- DOWN: 40,
92
- NUMPAD_ENTER: 108,
93
- COMMA: 188
94
- };
95
-
96
- // Additional public (exposed) methods
97
- var methods = {
98
- init: function(url_or_data_or_function, options) {
99
- var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
100
-
101
- return this.each(function () {
102
- $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
103
- });
104
- },
105
- clear: function() {
106
- this.data("tokenInputObject").clear();
107
- return this;
108
- },
109
- add: function(item) {
110
- this.data("tokenInputObject").add(item);
111
- return this;
112
- },
113
- remove: function(item) {
114
- this.data("tokenInputObject").remove(item);
115
- return this;
116
- },
117
- get: function() {
118
- return this.data("tokenInputObject").getTokens();
119
- }
120
- }
121
-
122
- // Expose the .tokenInput function to jQuery as a plugin
123
- $.fn.tokenInput = function (method) {
124
- // Method calling and initialization logic
125
- if(methods[method]) {
126
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
127
- } else {
128
- return methods.init.apply(this, arguments);
129
- }
130
- };
131
-
132
- // TokenList class for each input
133
- $.TokenList = function (input, url_or_data, settings) {
134
- //
135
- // Initialization
136
- //
137
-
138
- // Configure the data source
139
- if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
140
- // Set the url to query against
141
- settings.url = url_or_data;
142
-
143
- // If the URL is a function, evaluate it here to do our initalization work
144
- var url = computeURL();
145
-
146
- // Make a smart guess about cross-domain if it wasn't explicitly specified
147
- if(settings.crossDomain === undefined) {
148
- if(url.indexOf("://") === -1) {
149
- settings.crossDomain = false;
150
- } else {
151
- settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
152
- }
153
- }
154
- } else if(typeof(url_or_data) === "object") {
155
- // Set the local data to search through
156
- settings.local_data = url_or_data;
157
- }
158
-
159
- // Build class names
160
- if(settings.classes) {
161
- // Use custom class names
162
- settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
163
- } else if(settings.theme) {
164
- // Use theme-suffixed default class names
165
- settings.classes = {};
166
- $.each(DEFAULT_CLASSES, function(key, value) {
167
- settings.classes[key] = value + "-" + settings.theme;
168
- });
169
- } else {
170
- settings.classes = DEFAULT_CLASSES;
171
- }
172
-
173
-
174
- // Save the tokens
175
- var saved_tokens = [];
176
-
177
- // Keep track of the number of tokens in the list
178
- var token_count = 0;
179
-
180
- // Basic cache to save on db hits
181
- var cache = new $.TokenList.Cache();
182
-
183
- // Keep track of the timeout, old vals
184
- var timeout;
185
- var input_val;
186
-
187
- // Create a new text input an attach keyup events
188
- var input_box = $("<input type=\"text\" autocomplete=\"off\">")
189
- .css({
190
- outline: "none"
191
- })
192
- .attr("id", settings.idPrefix + input.id)
193
- .focus(function () {
194
- if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
195
- show_dropdown_hint();
196
- }
197
- })
198
- .blur(function () {
199
- hide_dropdown();
200
- $(this).val("");
201
- })
202
- .bind("keyup keydown blur update", resize_input)
203
- .keydown(function (event) {
204
- var previous_token;
205
- var next_token;
206
-
207
- switch(event.keyCode) {
208
- case KEY.LEFT:
209
- case KEY.RIGHT:
210
- case KEY.UP:
211
- case KEY.DOWN:
212
- if(!$(this).val()) {
213
- previous_token = input_token.prev();
214
- next_token = input_token.next();
215
-
216
- if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
217
- // Check if there is a previous/next token and it is selected
218
- if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
219
- deselect_token($(selected_token), POSITION.BEFORE);
220
- } else {
221
- deselect_token($(selected_token), POSITION.AFTER);
222
- }
223
- } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
224
- // We are moving left, select the previous token if it exists
225
- select_token($(previous_token.get(0)));
226
- } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
227
- // We are moving right, select the next token if it exists
228
- select_token($(next_token.get(0)));
229
- }
230
- } else {
231
- var dropdown_item = null;
232
-
233
- if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
234
- dropdown_item = $(selected_dropdown_item).next();
235
- } else {
236
- dropdown_item = $(selected_dropdown_item).prev();
237
- }
238
-
239
- if(dropdown_item.length) {
240
- select_dropdown_item(dropdown_item);
241
- }
242
- return false;
243
- }
244
- break;
245
-
246
- case KEY.BACKSPACE:
247
- previous_token = input_token.prev();
248
-
249
- if(!$(this).val().length) {
250
- if(selected_token) {
251
- delete_token($(selected_token));
252
- hidden_input.change();
253
- } else if(previous_token.length) {
254
- select_token($(previous_token.get(0)));
255
- }
256
-
257
- return false;
258
- } else if($(this).val().length === 1) {
259
- hide_dropdown();
260
- } else {
261
- // set a timeout just long enough to let this function finish.
262
- setTimeout(function(){do_search();}, 5);
263
- }
264
- break;
265
-
266
- case KEY.TAB:
267
- case KEY.ENTER:
268
- case KEY.NUMPAD_ENTER:
269
- case KEY.COMMA:
270
- if(selected_dropdown_item) {
271
- add_token($(selected_dropdown_item).data("tokeninput"));
272
- hidden_input.change();
273
- return false;
274
- }
275
- break;
276
-
277
- case KEY.ESCAPE:
278
- hide_dropdown();
279
- return true;
280
-
281
- default:
282
- if(String.fromCharCode(event.which)) {
283
- // set a timeout just long enough to let this function finish.
284
- setTimeout(function(){do_search();}, 5);
285
- }
286
- break;
287
- }
288
- });
289
-
290
- // Keep a reference to the original input box
291
- var hidden_input = $(input)
292
- .hide()
293
- .val("")
294
- .focus(function () {
295
- input_box.focus();
296
- })
297
- .blur(function () {
298
- input_box.blur();
299
- });
300
-
301
- // Keep a reference to the selected token and dropdown item
302
- var selected_token = null;
303
- var selected_token_index = 0;
304
- var selected_dropdown_item = null;
305
-
306
- // The list to store the token items in
307
- var token_list = $("<ul />")
308
- .addClass(settings.classes.tokenList)
309
- .click(function (event) {
310
- var li = $(event.target).closest("li");
311
- if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
312
- toggle_select_token(li);
313
- } else {
314
- // Deselect selected token
315
- if(selected_token) {
316
- deselect_token($(selected_token), POSITION.END);
317
- }
318
-
319
- // Focus input box
320
- input_box.focus();
321
- }
322
- })
323
- .mouseover(function (event) {
324
- var li = $(event.target).closest("li");
325
- if(li && selected_token !== this) {
326
- li.addClass(settings.classes.highlightedToken);
327
- }
328
- })
329
- .mouseout(function (event) {
330
- var li = $(event.target).closest("li");
331
- if(li && selected_token !== this) {
332
- li.removeClass(settings.classes.highlightedToken);
333
- }
334
- })
335
- .insertBefore(hidden_input);
336
-
337
- // The token holding the input box
338
- var input_token = $("<li />")
339
- .addClass(settings.classes.inputToken)
340
- .appendTo(token_list)
341
- .append(input_box);
342
-
343
- // The list to store the dropdown items in
344
- var dropdown = $("<div>")
345
- .addClass(settings.classes.dropdown)
346
- .appendTo("body")
347
- .hide();
348
-
349
- // Magic element to help us resize the text input
350
- var input_resizer = $("<tester/>")
351
- .insertAfter(input_box)
352
- .css({
353
- position: "absolute",
354
- top: -9999,
355
- left: -9999,
356
- width: "auto",
357
- fontSize: input_box.css("fontSize"),
358
- fontFamily: input_box.css("fontFamily"),
359
- fontWeight: input_box.css("fontWeight"),
360
- letterSpacing: input_box.css("letterSpacing"),
361
- whiteSpace: "nowrap"
362
- });
363
-
364
- // Pre-populate list if items exist
365
- hidden_input.val("");
366
- var li_data = settings.prePopulate || hidden_input.data("pre");
367
- if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
368
- li_data = settings.onResult.call(hidden_input, li_data);
369
- }
370
- if(li_data && li_data.length) {
371
- $.each(li_data, function (index, value) {
372
- insert_token(value);
373
- checkTokenLimit();
374
- });
375
- }
376
-
377
- // Initialization is done
378
- if($.isFunction(settings.onReady)) {
379
- settings.onReady.call();
380
- }
381
-
382
- //
383
- // Public functions
384
- //
385
-
386
- this.clear = function() {
387
- token_list.children("li").each(function() {
388
- if ($(this).children("input").length === 0) {
389
- delete_token($(this));
390
- }
391
- });
392
- }
393
-
394
- this.add = function(item) {
395
- add_token(item);
396
- }
397
-
398
- this.remove = function(item) {
399
- token_list.children("li").each(function() {
400
- if ($(this).children("input").length === 0) {
401
- var currToken = $(this).data("tokeninput");
402
- var match = true;
403
- for (var prop in item) {
404
- if (item[prop] !== currToken[prop]) {
405
- match = false;
406
- break;
407
- }
408
- }
409
- if (match) {
410
- delete_token($(this));
411
- }
412
- }
413
- });
414
- }
415
-
416
- this.getTokens = function() {
417
- return saved_tokens;
418
- }
419
-
420
- //
421
- // Private functions
422
- //
423
-
424
- function checkTokenLimit() {
425
- if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
426
- input_box.hide();
427
- hide_dropdown();
428
- return;
429
- }
430
- }
431
-
432
- function resize_input() {
433
- if(input_val === (input_val = input_box.val())) {return;}
434
-
435
- // Enter new content into resizer and resize input accordingly
436
- var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
437
- input_resizer.html(escaped);
438
- input_box.width(input_resizer.width() + 30);
439
- }
440
-
441
- function is_printable_character(keycode) {
442
- return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
443
- (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
444
- (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
445
- (keycode >= 219 && keycode <= 222)); // ( \ ) '
446
- }
447
-
448
- // Inner function to a token to the list
449
- function insert_token(item) {
450
- var this_token = settings.tokenFormatter(item);
451
- this_token = $(this_token)
452
- .addClass(settings.classes.token)
453
- .insertBefore(input_token);
454
-
455
- // The 'delete token' button
456
- $("<span>" + settings.deleteText + "</span>")
457
- .addClass(settings.classes.tokenDelete)
458
- .appendTo(this_token)
459
- .click(function () {
460
- delete_token($(this).parent());
461
- hidden_input.change();
462
- return false;
463
- });
464
-
465
- // Store data on the token
466
- var token_data = {"id": item.id};
467
- token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
468
- $.data(this_token.get(0), "tokeninput", item);
469
-
470
- // Save this token for duplicate checking
471
- saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
472
- selected_token_index++;
473
-
474
- // Update the hidden input
475
- update_hidden_input(saved_tokens, hidden_input);
476
-
477
- token_count += 1;
478
-
479
- // Check the token limit
480
- if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
481
- input_box.hide();
482
- hide_dropdown();
483
- }
484
-
485
- return this_token;
486
- }
487
-
488
- // Add a token to the token list based on user input
489
- function add_token (item) {
490
- var callback = settings.onAdd;
491
-
492
- // See if the token already exists and select it if we don't want duplicates
493
- if(token_count > 0 && settings.preventDuplicates) {
494
- var found_existing_token = null;
495
- token_list.children().each(function () {
496
- var existing_token = $(this);
497
- var existing_data = $.data(existing_token.get(0), "tokeninput");
498
- if(existing_data && existing_data.id === item.id) {
499
- found_existing_token = existing_token;
500
- return false;
501
- }
502
- });
503
-
504
- if(found_existing_token) {
505
- select_token(found_existing_token);
506
- input_token.insertAfter(found_existing_token);
507
- input_box.focus();
508
- return;
509
- }
510
- }
511
-
512
- // Insert the new tokens
513
- if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
514
- insert_token(item);
515
- checkTokenLimit();
516
- }
517
-
518
- // Clear input box
519
- input_box.val("");
520
-
521
- // Don't show the help dropdown, they've got the idea
522
- hide_dropdown();
523
-
524
- // Execute the onAdd callback if defined
525
- if($.isFunction(callback)) {
526
- callback.call(hidden_input,item);
527
- }
528
- }
529
-
530
- // Select a token in the token list
531
- function select_token (token) {
532
- token.addClass(settings.classes.selectedToken);
533
- selected_token = token.get(0);
534
-
535
- // Hide input box
536
- input_box.val("");
537
-
538
- // Hide dropdown if it is visible (eg if we clicked to select token)
539
- hide_dropdown();
540
- }
541
-
542
- // Deselect a token in the token list
543
- function deselect_token (token, position) {
544
- token.removeClass(settings.classes.selectedToken);
545
- selected_token = null;
546
-
547
- if(position === POSITION.BEFORE) {
548
- input_token.insertBefore(token);
549
- selected_token_index--;
550
- } else if(position === POSITION.AFTER) {
551
- input_token.insertAfter(token);
552
- selected_token_index++;
553
- } else {
554
- input_token.appendTo(token_list);
555
- selected_token_index = token_count;
556
- }
557
-
558
- // Show the input box and give it focus again
559
- input_box.focus();
560
- }
561
-
562
- // Toggle selection of a token in the token list
563
- function toggle_select_token(token) {
564
- var previous_selected_token = selected_token;
565
-
566
- if(selected_token) {
567
- deselect_token($(selected_token), POSITION.END);
568
- }
569
-
570
- if(previous_selected_token === token.get(0)) {
571
- deselect_token(token, POSITION.END);
572
- } else {
573
- select_token(token);
574
- }
575
- }
576
-
577
- // Delete a token from the token list
578
- function delete_token (token) {
579
- // Remove the id from the saved list
580
- var token_data = $.data(token.get(0), "tokeninput");
581
- var callback = settings.onDelete;
582
-
583
- var index = token.prevAll().length;
584
- if(index > selected_token_index) index--;
585
-
586
- // Delete the token
587
- token.remove();
588
- selected_token = null;
589
-
590
- // Show the input box and give it focus again
591
- input_box.focus();
592
-
593
- // Remove this token from the saved list
594
- saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
595
- if(index < selected_token_index) selected_token_index--;
596
-
597
- // Update the hidden input
598
- update_hidden_input(saved_tokens, hidden_input);
599
-
600
- token_count -= 1;
601
-
602
- if(settings.tokenLimit !== null) {
603
- input_box
604
- .show()
605
- .val("")
606
- .focus();
607
- }
608
-
609
- // Execute the onDelete callback if defined
610
- if($.isFunction(callback)) {
611
- callback.call(hidden_input,token_data);
612
- }
613
- }
614
-
615
- // Update the hidden input box value
616
- function update_hidden_input(saved_tokens, hidden_input) {
617
- var token_values = $.map(saved_tokens, function (el) {
618
- return el[settings.tokenValue];
619
- });
620
- hidden_input.val(token_values.join(settings.tokenDelimiter));
621
-
622
- }
623
-
624
- // Hide and clear the results dropdown
625
- function hide_dropdown () {
626
- dropdown.hide().empty();
627
- selected_dropdown_item = null;
628
- }
629
-
630
- function show_dropdown() {
631
- dropdown
632
- .css({
633
- position: "absolute",
634
- top: $(token_list).offset().top + $(token_list).outerHeight(),
635
- left: $(token_list).offset().left,
636
- zindex: 999
637
- })
638
- .show();
639
- }
640
-
641
- function show_dropdown_searching () {
642
- if(settings.searchingText) {
643
- dropdown.html("<p>"+settings.searchingText+"</p>");
644
- show_dropdown();
645
- }
646
- }
647
-
648
- function show_dropdown_hint () {
649
- if(settings.hintText) {
650
- dropdown.html("<p>"+settings.hintText+"</p>");
651
- show_dropdown();
652
- }
653
- }
654
-
655
- // Highlight the query part of the search term
656
- function highlight_term(value, term) {
657
- return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
658
- }
659
-
660
- function find_value_and_highlight_term(template, value, term) {
661
- return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
662
- }
663
-
664
- // Populate the results dropdown with some results
665
- function populate_dropdown (query, results) {
666
- if(results && results.length) {
667
- dropdown.empty();
668
- var dropdown_ul = $("<ul>")
669
- .appendTo(dropdown)
670
- .mouseover(function (event) {
671
- select_dropdown_item($(event.target).closest("li"));
672
- })
673
- .mousedown(function (event) {
674
- add_token($(event.target).closest("li").data("tokeninput"));
675
- hidden_input.change();
676
- return false;
677
- })
678
- .hide();
679
-
680
- $.each(results, function(index, value) {
681
- var this_li = settings.resultsFormatter(value);
682
-
683
- this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);
684
-
685
- this_li = $(this_li).appendTo(dropdown_ul);
686
-
687
- if(index % 2) {
688
- this_li.addClass(settings.classes.dropdownItem);
689
- } else {
690
- this_li.addClass(settings.classes.dropdownItem2);
691
- }
692
-
693
- if(index === 0) {
694
- select_dropdown_item(this_li);
695
- }
696
-
697
- $.data(this_li.get(0), "tokeninput", value);
698
- });
699
-
700
- show_dropdown();
701
-
702
- if(settings.animateDropdown) {
703
- dropdown_ul.slideDown("fast");
704
- } else {
705
- dropdown_ul.show();
706
- }
707
- } else {
708
- if(settings.noResultsText) {
709
- dropdown.html("<p>"+settings.noResultsText+"</p>");
710
- show_dropdown();
711
- }
712
- }
713
- }
714
-
715
- // Highlight an item in the results dropdown
716
- function select_dropdown_item (item) {
717
- if(item) {
718
- if(selected_dropdown_item) {
719
- deselect_dropdown_item($(selected_dropdown_item));
720
- }
721
-
722
- item.addClass(settings.classes.selectedDropdownItem);
723
- selected_dropdown_item = item.get(0);
724
- }
725
- }
726
-
727
- // Remove highlighting from an item in the results dropdown
728
- function deselect_dropdown_item (item) {
729
- item.removeClass(settings.classes.selectedDropdownItem);
730
- selected_dropdown_item = null;
731
- }
732
-
733
- // Do a search and show the "searching" dropdown if the input is longer
734
- // than settings.minChars
735
- function do_search() {
736
- var query = input_box.val().toLowerCase();
737
-
738
- if(query && query.length) {
739
- if(selected_token) {
740
- deselect_token($(selected_token), POSITION.AFTER);
741
- }
742
-
743
- if(query.length >= settings.minChars) {
744
- show_dropdown_searching();
745
- clearTimeout(timeout);
746
-
747
- timeout = setTimeout(function(){
748
- run_search(query);
749
- }, settings.searchDelay);
750
- } else {
751
- hide_dropdown();
752
- }
753
- }
754
- }
755
-
756
- // Do the actual search
757
- function run_search(query) {
758
- var cache_key = query + computeURL();
759
- var cached_results = cache.get(cache_key);
760
- if(cached_results) {
761
- populate_dropdown(query, cached_results);
762
- } else {
763
- // Are we doing an ajax search or local data search?
764
- if(settings.url) {
765
- var url = computeURL();
766
- // Extract exisiting get params
767
- var ajax_params = {};
768
- ajax_params.data = {};
769
- if(url.indexOf("?") > -1) {
770
- var parts = url.split("?");
771
- ajax_params.url = parts[0];
772
-
773
- var param_array = parts[1].split("&");
774
- $.each(param_array, function (index, value) {
775
- var kv = value.split("=");
776
- ajax_params.data[kv[0]] = kv[1];
777
- });
778
- } else {
779
- ajax_params.url = url;
780
- }
781
-
782
- // Prepare the request
783
- ajax_params.data[settings.queryParam] = query;
784
- ajax_params.type = settings.method;
785
- ajax_params.dataType = settings.contentType;
786
- if(settings.crossDomain) {
787
- ajax_params.dataType = "jsonp";
788
- }
789
-
790
- // Attach the success callback
791
- ajax_params.success = function(results) {
792
- if($.isFunction(settings.onResult)) {
793
- results = settings.onResult.call(hidden_input, results);
794
- }
795
- cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results);
796
-
797
- // only populate the dropdown if the results are associated with the active search query
798
- if(input_box.val().toLowerCase() === query) {
799
- populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
800
- }
801
- };
802
-
803
- // Make the request
804
- $.ajax(ajax_params);
805
- } else if(settings.local_data) {
806
- // Do the search through local data
807
- var results = $.grep(settings.local_data, function (row) {
808
- return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
809
- });
810
-
811
- if($.isFunction(settings.onResult)) {
812
- results = settings.onResult.call(hidden_input, results);
813
- }
814
- cache.add(cache_key, results);
815
- populate_dropdown(query, results);
816
- }
817
- }
818
- }
819
-
820
- // compute the dynamic URL
821
- function computeURL() {
822
- var url = settings.url;
823
- if(typeof settings.url == 'function') {
824
- url = settings.url.call();
825
- }
826
- return url;
827
- }
828
- };
829
-
830
- // Really basic cache for the results
831
- $.TokenList.Cache = function (options) {
832
- var settings = $.extend({
833
- max_size: 500
834
- }, options);
835
-
836
- var data = {};
837
- var size = 0;
838
-
839
- var flush = function () {
840
- data = {};
841
- size = 0;
842
- };
843
-
844
- this.add = function (query, results) {
845
- if(size > settings.max_size) {
846
- flush();
847
- }
848
-
849
- if(!data[query]) {
850
- size += 1;
851
- }
852
-
853
- data[query] = results;
854
- };
855
-
856
- this.get = function (query) {
857
- return data[query];
858
- };
859
- };
860
- }(jQuery));