spree_variant_options 0.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/Gemfile +2 -0
- data/LICENSE +25 -0
- data/README.md +129 -0
- data/Rakefile +26 -0
- data/Versionfile +4 -0
- data/app/models/product_decorator.rb +27 -0
- data/app/views/products/_cart_form.html.erb +32 -0
- data/app/views/products/_variant_options.html.erb +43 -0
- data/config/cucumber.yml +8 -0
- data/features/step_definitions/variant_options.rb +125 -0
- data/features/step_definitions/web_steps.rb +138 -0
- data/features/support/env.rb +56 -0
- data/features/support/factories.rb +45 -0
- data/features/support/helper_methods.rb +28 -0
- data/features/support/paths.rb +36 -0
- data/features/support/selectors.rb +39 -0
- data/features/variant_options.feature +108 -0
- data/lib/dummy_hooks/after_migrate.rb.sample +1 -0
- data/lib/dummy_hooks/before_migrate.rb +1 -0
- data/lib/spree_variant_options.rb +26 -0
- data/lib/spree_variant_options/version.rb +3 -0
- data/public/images/out-of-stock.png +0 -0
- data/public/javascripts/product.js +38 -0
- data/public/javascripts/variant_options.js +211 -0
- data/public/stylesheets/variant_options.css +68 -0
- data/spree_variant_options.gemspec +37 -0
- data/test/test_helper.rb +22 -0
- data/test/unit/product_test.rb +46 -0
- metadata +217 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
|
2
|
+
# It is recommended to regenerate this file in the future when you upgrade to a
|
3
|
+
# newer version of cucumber-rails. Consider adding your own code to a new file
|
4
|
+
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
|
5
|
+
# files.
|
6
|
+
|
7
|
+
require 'spork'
|
8
|
+
|
9
|
+
ENV["RAILS_ROOT"] = File.expand_path("../../../test/dummy", __FILE__)
|
10
|
+
|
11
|
+
Spork.prefork do
|
12
|
+
require 'cucumber/rails'
|
13
|
+
require 'capybara/rails'
|
14
|
+
require 'capybara/cucumber'
|
15
|
+
require 'capybara/session'
|
16
|
+
require 'factory_girl'
|
17
|
+
require 'faker'
|
18
|
+
|
19
|
+
I18n.reload!
|
20
|
+
|
21
|
+
Capybara.default_driver = :selenium
|
22
|
+
Capybara.default_selector = :css
|
23
|
+
end
|
24
|
+
|
25
|
+
Spork.each_run do
|
26
|
+
|
27
|
+
# By default, any exception happening in your Rails application will bubble up
|
28
|
+
# to Cucumber so that your scenario will fail. This is a different from how
|
29
|
+
# your application behaves in the production environment, where an error page will
|
30
|
+
# be rendered instead.
|
31
|
+
#
|
32
|
+
# Sometimes we want to override this default behaviour and allow Rails to rescue
|
33
|
+
# exceptions and display an error page (just like when the app is running in production).
|
34
|
+
# Typical scenarios where you want to do this is when you test your error pages.
|
35
|
+
# There are two ways to allow Rails to rescue exceptions:
|
36
|
+
#
|
37
|
+
# 1) Tag your scenario (or feature) with @allow-rescue
|
38
|
+
#
|
39
|
+
# 2) Set the value below to true. Beware that doing this globally is not
|
40
|
+
# recommended as it will mask a lot of errors for you!
|
41
|
+
#
|
42
|
+
ActionController::Base.allow_rescue = false
|
43
|
+
|
44
|
+
# doesn't seem to work :/
|
45
|
+
Cucumber::Rails::World.use_transactional_fixtures = false
|
46
|
+
# Remove/comment out the lines below if your app doesn't have a database.
|
47
|
+
# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead.
|
48
|
+
begin
|
49
|
+
DatabaseCleaner.strategy = :transaction
|
50
|
+
rescue NameError
|
51
|
+
raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
|
52
|
+
end
|
53
|
+
|
54
|
+
Dir["#{File.expand_path("../../../", __FILE__)}/test/support/**/*.rb"].each { |f| require f }
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
|
3
|
+
factory :product do
|
4
|
+
name "Very Wearily Variantly"
|
5
|
+
description "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh."
|
6
|
+
available_on { Time.zone.now - 1.day }
|
7
|
+
permalink "very-wearily-variantly"
|
8
|
+
price 17.00
|
9
|
+
count_on_hand 10
|
10
|
+
end
|
11
|
+
|
12
|
+
factory :product_with_variants, :parent => :product do
|
13
|
+
after_create { |product|
|
14
|
+
sizes = %w(Small Medium Large X-Large).map{|i| Factory.create(:option_value, :presentation => i) }
|
15
|
+
colors = %w(Red Green Blue Yellow Purple Gray Black White).map{|i|
|
16
|
+
Factory.create(:option_value, :presentation => i, :option_type => OptionType.find_by_name("color") || Factory.create(:option_type, :presentation => "Color"))
|
17
|
+
}
|
18
|
+
product.variants = sizes.map{|i| colors.map{|j| Factory.create(:variant, :product => product, :option_values => [i, j]) }}.flatten
|
19
|
+
product.option_types = OptionType.where(:name => %w(size color))
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
factory :variant do
|
24
|
+
product { Product.last || Factory.create(:product) }
|
25
|
+
option_values { [OptionValue.last || Factory.create(:option_value)] }
|
26
|
+
sequence(:sku) { |n| "ROR-#{1000 + n}" }
|
27
|
+
sequence(:price) { |n| 19.99 + n }
|
28
|
+
cost_price 17.00
|
29
|
+
count_on_hand 10
|
30
|
+
end
|
31
|
+
|
32
|
+
factory :option_type do
|
33
|
+
presentation "Size"
|
34
|
+
name { presentation.downcase }
|
35
|
+
sequence(:position) {|n| n }
|
36
|
+
end
|
37
|
+
|
38
|
+
factory :option_value do
|
39
|
+
presentation "Large"
|
40
|
+
name { presentation.downcase }
|
41
|
+
option_type { OptionType.last || Factory.create(:option_type) }
|
42
|
+
sequence(:position) {|n| n }
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HelperMethods
|
2
|
+
|
3
|
+
# Checks for missing translations after each test
|
4
|
+
def teardown
|
5
|
+
unless source.blank?
|
6
|
+
matches = source.match(/translation[\s-]+missing[^"]*/) || []
|
7
|
+
assert_equal 0, matches.length, "Translation Missing! - #{matches[0]}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# An assertion for ensuring content has made it to the page.
|
12
|
+
#
|
13
|
+
# assert_seen "Site Title"
|
14
|
+
# assert_seen "Peanut Butter Jelly Time", :within => ".post-title h1"
|
15
|
+
#
|
16
|
+
def assert_seen(text, opts={})
|
17
|
+
if opts[:within]
|
18
|
+
within(opts[:within]) do
|
19
|
+
assert has_content?(text)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
assert has_content?(text)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
World(HelperMethods)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
# Maps a name to a path. Used by the
|
3
|
+
#
|
4
|
+
# When /^I go to (.+)$/ do |page_name|
|
5
|
+
#
|
6
|
+
# step definition in web_steps.rb
|
7
|
+
#
|
8
|
+
def path_to(page_name)
|
9
|
+
case page_name
|
10
|
+
|
11
|
+
when /the home\s?page/
|
12
|
+
'/'
|
13
|
+
when /the new post page/
|
14
|
+
new_post_path
|
15
|
+
|
16
|
+
|
17
|
+
# Add more mappings here.
|
18
|
+
# Here is an example that pulls values out of the Regexp:
|
19
|
+
#
|
20
|
+
# when /^(.*)'s profile page$/i
|
21
|
+
# user_profile_path(User.find_by_login($1))
|
22
|
+
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
page_name =~ /the (.*) page/
|
26
|
+
path_components = $1.split(/\s+/)
|
27
|
+
self.send(path_components.push('path').join('_').to_sym)
|
28
|
+
rescue Object => e
|
29
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
30
|
+
"Now, go and add a mapping in #{__FILE__}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
World(NavigationHelpers)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module HtmlSelectorsHelpers
|
2
|
+
# Maps a name to a selector. Used primarily by the
|
3
|
+
#
|
4
|
+
# When /^(.+) within (.+)$/ do |step, scope|
|
5
|
+
#
|
6
|
+
# step definitions in web_steps.rb
|
7
|
+
#
|
8
|
+
def selector_for(locator)
|
9
|
+
case locator
|
10
|
+
|
11
|
+
when /the page/
|
12
|
+
"html > body"
|
13
|
+
|
14
|
+
# Add more mappings here.
|
15
|
+
# Here is an example that pulls values out of the Regexp:
|
16
|
+
#
|
17
|
+
# when /the (notice|error|info) flash/
|
18
|
+
# ".flash.#{$1}"
|
19
|
+
|
20
|
+
# You can also return an array to use a different selector
|
21
|
+
# type, like:
|
22
|
+
#
|
23
|
+
# when /the header/
|
24
|
+
# [:xpath, "//header"]
|
25
|
+
|
26
|
+
# This allows you to provide a quoted selector as the scope
|
27
|
+
# for "within" steps as was previously the default for the
|
28
|
+
# web steps:
|
29
|
+
when /"(.+)"/
|
30
|
+
$1
|
31
|
+
|
32
|
+
else
|
33
|
+
raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
|
34
|
+
"Now, go and add a mapping in #{__FILE__}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
World(HtmlSelectorsHelpers)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
@no-txn @javascript
|
2
|
+
Feature: Products should have variant options
|
3
|
+
A Product's variants should be broken out into options
|
4
|
+
|
5
|
+
Scenario: Display options when visiting a product
|
6
|
+
Given I have a product with variants
|
7
|
+
And I'm on the product page for the first product
|
8
|
+
Then the source should contain the options hash
|
9
|
+
And I should see enabled links for the first option type
|
10
|
+
And I should see disabled links for the second option type
|
11
|
+
And I should have a hidden input for the selected variant
|
12
|
+
And the add to cart button should be disabled
|
13
|
+
|
14
|
+
Scenario: Interact with options for a product
|
15
|
+
Given I have a product with variants
|
16
|
+
And I'm on the product page for the first product
|
17
|
+
When I follow "Small" within the first set of options
|
18
|
+
Then I should see enabled links for the second option type
|
19
|
+
And the add to cart button should be disabled
|
20
|
+
When I follow "Green" within the second set of options
|
21
|
+
Then the add to cart button should be enabled
|
22
|
+
When I follow "Medium" within the first set of options
|
23
|
+
And I should see enabled links for the second option type
|
24
|
+
And the add to cart button should be disabled
|
25
|
+
When I follow "Red" within the second set of options
|
26
|
+
And the add to cart button should be enabled
|
27
|
+
|
28
|
+
Scenario: Should show out of stock for appropriate variants
|
29
|
+
Given I have a product with variants
|
30
|
+
And the "Small Green" variant is out of stock
|
31
|
+
And I'm on the product page for the first product
|
32
|
+
When I follow "Small" within the first set of options
|
33
|
+
Then I should see an out-of-stock link for "Green"
|
34
|
+
And I should see an in-stock link for "Red, Blue, Black, White, Gray"
|
35
|
+
|
36
|
+
Scenario: Should clear current selection
|
37
|
+
Given I have a product with variants
|
38
|
+
And I'm on the product page for the first product
|
39
|
+
When I follow "Small" within the first set of options
|
40
|
+
And I click the current clear button
|
41
|
+
Then I should see disabled links for the second option type
|
42
|
+
And I should see enabled links for the first option type
|
43
|
+
|
44
|
+
Scenario: Should clear current selection and maintain parent selection
|
45
|
+
Given I have a product with variants
|
46
|
+
And I'm on the product page for the first product
|
47
|
+
When I follow "Small" within the first set of options
|
48
|
+
And I follow "Green" within the second set of options
|
49
|
+
Then the add to cart button should be enabled
|
50
|
+
And I click the last clear button
|
51
|
+
Then I should see "Small" selected within the first set of options
|
52
|
+
And I should see enabled links for the second option type
|
53
|
+
And the add to cart button should be disabled
|
54
|
+
|
55
|
+
Scenario: Should clear current selection and parent selection
|
56
|
+
Given I have a product with variants
|
57
|
+
And I'm on the product page for the first product
|
58
|
+
When I follow "Small" within the first set of options
|
59
|
+
And I follow "Green" within the second set of options
|
60
|
+
Then the add to cart button should be enabled
|
61
|
+
And I click the first clear button
|
62
|
+
Then I should not see a selected option
|
63
|
+
And I should see disabled links for the second option type
|
64
|
+
And I should see enabled links for the first option type
|
65
|
+
And the add to cart button should be disabled
|
66
|
+
|
67
|
+
Scenario: Should add proper variant to cart
|
68
|
+
Given I have a product with variants
|
69
|
+
And I'm on the product page for the first product
|
70
|
+
When I follow "Small" within the first set of options
|
71
|
+
And I follow "Green" within the second set of options
|
72
|
+
Then the add to cart button should be enabled
|
73
|
+
And I press "Add To Cart"
|
74
|
+
Then I should be on the cart page
|
75
|
+
And I should see "Size: Small, Color: Green"
|
76
|
+
|
77
|
+
Scenario: Should auto-select variant if its the only option
|
78
|
+
Given I have a product with variants
|
79
|
+
And I have an "XXL Turquoise" variant
|
80
|
+
And I'm on the product page for the first product
|
81
|
+
When I follow "XXL" within the first set of options
|
82
|
+
Then I should see "Turquoise" selected within the second set of options
|
83
|
+
And the add to cart button should be enabled
|
84
|
+
When I follow "Small" within the first set of options
|
85
|
+
Then the add to cart button should be disabled
|
86
|
+
|
87
|
+
Scenario: Should adjust price according to variant
|
88
|
+
Given I have a product with variants
|
89
|
+
And I have an "XXS Turquoise" variant for $29.99
|
90
|
+
And I have an "XXS Pink" variant for $24.99
|
91
|
+
And I'm on the product page for the first product
|
92
|
+
When I follow "XXS" within the first set of options
|
93
|
+
Then I should see "$24.99 - $29.99" in the price
|
94
|
+
When I follow "Turquoise" within the second set of options
|
95
|
+
Then I should see "$29.99" in the price
|
96
|
+
And the add to cart button should be enabled
|
97
|
+
When I follow "Pink" within the second set of options
|
98
|
+
Then I should see "$24.99" in the price
|
99
|
+
And the add to cart button should be enabled
|
100
|
+
|
101
|
+
Scenario: Should show variant images when a selection is made
|
102
|
+
Given I have a product with variants
|
103
|
+
And I'm on the product page for the first product
|
104
|
+
When I follow "Small" within the first set of options
|
105
|
+
And I follow "Green" within the second set of options
|
106
|
+
Then the add to cart button should be enabled
|
107
|
+
And I should see "Small Green" in the variant images label # its hidden but it's there!
|
108
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
rake "db:migrate db:seed db:sample", :env => "development"
|
@@ -0,0 +1 @@
|
|
1
|
+
rake "spree_core:install spree_sample:install"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spree_core'
|
2
|
+
require 'spree_sample' unless Rails.env == 'production'
|
3
|
+
|
4
|
+
module SpreeVariantOptions
|
5
|
+
|
6
|
+
class Engine < Rails::Engine
|
7
|
+
|
8
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
9
|
+
|
10
|
+
initializer "static assets" do |app|
|
11
|
+
app.middleware.insert_before ::Rack::Lock, ::ActionDispatch::Static, "#{config.root}/public"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.activate
|
15
|
+
|
16
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator.rb")) do |c|
|
17
|
+
Rails.env.production? ? require(c) : load(c)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
config.to_prepare &method(:activate).to_proc
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
Binary file
|
@@ -0,0 +1,38 @@
|
|
1
|
+
var add_image_handlers = function() {
|
2
|
+
$("#main-image").data('selectedThumb', $('#main-image img').attr('src'));
|
3
|
+
$('ul.thumbnails li').eq(0).addClass('selected');
|
4
|
+
$('ul.thumbnails li a').click(function() {
|
5
|
+
$("#main-image").data('selectedThumb', $(this).attr('href'));
|
6
|
+
$('ul.thumbnails li').removeClass('selected');
|
7
|
+
$(this).parent('li').addClass('selected');
|
8
|
+
return false;
|
9
|
+
}).hover(function() {
|
10
|
+
$('#main-image img').attr('src', $(this).attr('href').replace('mini', 'product'));
|
11
|
+
}, function() {
|
12
|
+
$('#main-image img').attr('src', $("#main-image").data('selectedThumb'));
|
13
|
+
});
|
14
|
+
};
|
15
|
+
|
16
|
+
var select_variant = function(vid, text) {
|
17
|
+
jQuery("#variant-thumbnails").empty();
|
18
|
+
jQuery("#variant-images span").html(text);
|
19
|
+
if (images[vid].length > 0) {
|
20
|
+
$.each(images[vid], function(i, link) {
|
21
|
+
jQuery("#variant-thumbnails").append('<li>' + link + '</li>');
|
22
|
+
});
|
23
|
+
jQuery("#variant-images").show();
|
24
|
+
} else {
|
25
|
+
jQuery("#variant-images").hide();
|
26
|
+
}
|
27
|
+
add_image_handlers();
|
28
|
+
var link = jQuery("#variant-thumbnails a")[0];
|
29
|
+
jQuery("#main-image img").attr({src: jQuery(link).attr('href')});
|
30
|
+
jQuery('ul.thumbnails li').removeClass('selected');
|
31
|
+
jQuery(link).parent('li').addClass('selected');
|
32
|
+
}
|
33
|
+
|
34
|
+
jQuery(document).ready(function() {
|
35
|
+
add_image_handlers();
|
36
|
+
jQuery("#variant-thumbnails").empty();
|
37
|
+
jQuery("#variant-images").hide();
|
38
|
+
});
|
@@ -0,0 +1,211 @@
|
|
1
|
+
$.extend({
|
2
|
+
keys: function(obj){
|
3
|
+
var a = [];
|
4
|
+
$.each(obj, function(k){ a.push(k) });
|
5
|
+
return a;
|
6
|
+
}
|
7
|
+
});
|
8
|
+
|
9
|
+
if (!Array.indexOf) Array.prototype.indexOf = function(obj) {
|
10
|
+
for(var i = 0; i < this.length; i++){
|
11
|
+
if(this[i] == obj) {
|
12
|
+
return i;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
return -1;
|
16
|
+
}
|
17
|
+
|
18
|
+
if (!Array.find_matches) Array.find_matches = function(a) {
|
19
|
+
var i, m = [];
|
20
|
+
a = a.sort();
|
21
|
+
i = a.length
|
22
|
+
while(i--) {
|
23
|
+
if (a[i - 1] == a[i]) {
|
24
|
+
m.push(a[i]);
|
25
|
+
}
|
26
|
+
}
|
27
|
+
if (m.length == 0) {
|
28
|
+
return false;
|
29
|
+
}
|
30
|
+
return m;
|
31
|
+
}
|
32
|
+
|
33
|
+
function VariantOptions(options) {
|
34
|
+
|
35
|
+
var options = options;
|
36
|
+
var variant, divs, parent, index = 0;
|
37
|
+
var selection = [];
|
38
|
+
|
39
|
+
function init() {
|
40
|
+
divs = $('#product-variants .variant-options');
|
41
|
+
disable(divs.find('a.option-value').addClass('locked'));
|
42
|
+
update();
|
43
|
+
enable(parent.find('a.option-value'));
|
44
|
+
toggle();
|
45
|
+
$('.clear-option a.clear-button').hide().click(handle_clear);
|
46
|
+
}
|
47
|
+
|
48
|
+
function get_index(parent) {
|
49
|
+
return parseInt($(parent).attr('class').replace(/[^\d]/g, ''));
|
50
|
+
}
|
51
|
+
|
52
|
+
function update(i) {
|
53
|
+
index = isNaN(i) ? index : i;
|
54
|
+
parent = $(divs.get(index));
|
55
|
+
buttons = parent.find('a.option-value');
|
56
|
+
parent.find('a.clear-button').hide();
|
57
|
+
}
|
58
|
+
|
59
|
+
function disable(btns) {
|
60
|
+
return btns.removeClass('selected');
|
61
|
+
}
|
62
|
+
|
63
|
+
function enable(btns) {
|
64
|
+
return btns.not('.unavailable').removeClass('locked').unbind('click').filter('.in-stock').click(handle_click).filter('.auto-click').removeClass('auto-click').click();
|
65
|
+
}
|
66
|
+
|
67
|
+
function advance() {
|
68
|
+
index++
|
69
|
+
update();
|
70
|
+
inventory(buttons.removeClass('locked'));
|
71
|
+
enable(buttons.filter('.in-stock'));
|
72
|
+
}
|
73
|
+
|
74
|
+
function inventory(btns) {
|
75
|
+
var keys, variants, count = 0, selected = {};
|
76
|
+
var sels = $.map(divs.find('a.selected'), function(i) { return i.rel });
|
77
|
+
$.each(sels, function(key, value) {
|
78
|
+
key = value.split('-');
|
79
|
+
var v = options[key[0]][key[1]];
|
80
|
+
keys = $.keys(v);
|
81
|
+
var m = Array.find_matches(selection.concat(keys));
|
82
|
+
if (selection.length == 0) {
|
83
|
+
selection = keys;
|
84
|
+
} else if (m) {
|
85
|
+
selection = m;
|
86
|
+
}
|
87
|
+
});
|
88
|
+
btns.removeClass('in-stock out-of-stock unavailable').each(function(i, element) {
|
89
|
+
variants = get_variant_objects(element.rel);
|
90
|
+
keys = $.keys(variants);
|
91
|
+
if (keys.length == 0) {
|
92
|
+
disable($(element).addClass('unavailable locked').unbind('click'));
|
93
|
+
} else if (keys.length == 1) {
|
94
|
+
_var = variants[keys[0]];
|
95
|
+
$(element).addClass(_var.count ? selection.length == 1 ? 'in-stock auto-click' : 'in-stock' : 'out-of-stock');
|
96
|
+
} else {
|
97
|
+
$.each(variants, function(key, value) { count += value.count });
|
98
|
+
$(element).addClass(count ? 'in-stock' : 'out-of-stock');
|
99
|
+
}
|
100
|
+
});
|
101
|
+
}
|
102
|
+
|
103
|
+
function get_variant_objects(rels) {
|
104
|
+
var i, ids, obj, variants = {};
|
105
|
+
if (typeof(rels) == 'string') { rels = [rels]; }
|
106
|
+
var otid, ovid, opt, opv;
|
107
|
+
i = rels.length;
|
108
|
+
try {
|
109
|
+
while (i--) {
|
110
|
+
ids = rels[i].split('-');
|
111
|
+
otid = ids[0];
|
112
|
+
ovid = ids[1];
|
113
|
+
opt = options[otid];
|
114
|
+
if (opt) {
|
115
|
+
opv = opt[ovid];
|
116
|
+
ids = $.keys(opv);
|
117
|
+
if (opv && ids.length) {
|
118
|
+
var j = ids.length;
|
119
|
+
while (j--) {
|
120
|
+
obj = opv[ids[j]];
|
121
|
+
if (obj && $.keys(obj).length && 0 <= selection.indexOf(obj.id.toString())) {
|
122
|
+
variants[obj.id] = obj;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
} catch(error) {
|
129
|
+
//console.log(error);
|
130
|
+
}
|
131
|
+
return variants;
|
132
|
+
}
|
133
|
+
|
134
|
+
function to_f(string) {
|
135
|
+
return parseFloat(string.replace(/[^\d\.]/g, ''));
|
136
|
+
}
|
137
|
+
|
138
|
+
function find_variant() {
|
139
|
+
var selected = divs.find('a.selected');
|
140
|
+
var variants = get_variant_objects(selected.get(0).rel);
|
141
|
+
if (selected.length == divs.length) {
|
142
|
+
return variant = variants[selection[0]];
|
143
|
+
} else {
|
144
|
+
var prices = [];
|
145
|
+
$.each(variants, function(key, value) { prices.push(value.price) });
|
146
|
+
prices = $.unique(prices).sort(function(a, b) {
|
147
|
+
return to_f(a) < to_f(b) ? -1 : 1;
|
148
|
+
});
|
149
|
+
if (prices.length == 1) {
|
150
|
+
$('.prices .price').html('<span class="price assumed">' + prices[0] + '</span>');
|
151
|
+
} else {
|
152
|
+
$('.prices .price').html('<span class="price from">' + prices[0] + '</span> - <span class="price to">' + prices[prices.length - 1] + '</span>');
|
153
|
+
}
|
154
|
+
return false;
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
function toggle() {
|
159
|
+
if (variant) {
|
160
|
+
$('#variant_id').val(variant.id);
|
161
|
+
$('.prices .price').removeClass('unselected').text(variant.price);
|
162
|
+
$('button[type=submit]').attr('disabled', false).fadeTo(100, 1);
|
163
|
+
try {
|
164
|
+
select_variant(variant.id, $.map($('a.selected'), function(i) { return $(i).text() }).join(" "));
|
165
|
+
} catch(error) {
|
166
|
+
// depends on modified version of product.js
|
167
|
+
}
|
168
|
+
} else {
|
169
|
+
$('#variant_id').val('');
|
170
|
+
$('button[type=submit]').attr('disabled', true).fadeTo(0, 0.5);
|
171
|
+
$('.prices .price').addClass('unselected').text('(select)');
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
function clear(i) {
|
176
|
+
variant = null;
|
177
|
+
update(i);
|
178
|
+
enable(buttons.removeClass('selected'));
|
179
|
+
toggle();
|
180
|
+
parent.nextAll().each(function(index, element) {
|
181
|
+
disable($(element).find('a.option-value').show().removeClass('in-stock out-of-stock').addClass('locked').unbind('click'));
|
182
|
+
$(element).find('a.clear-button').hide();
|
183
|
+
});
|
184
|
+
}
|
185
|
+
|
186
|
+
|
187
|
+
function handle_clear(evt) {
|
188
|
+
evt.preventDefault();
|
189
|
+
clear(get_index(this));
|
190
|
+
}
|
191
|
+
|
192
|
+
function handle_click(evt) {
|
193
|
+
evt.preventDefault();
|
194
|
+
variant = null;
|
195
|
+
selection = [];
|
196
|
+
var a = $(this);
|
197
|
+
if (!parent.has(a).length) {
|
198
|
+
clear(divs.index(a.parents('.variant-options:first')));
|
199
|
+
}
|
200
|
+
disable(buttons);
|
201
|
+
var a = enable(a.addClass('selected'));
|
202
|
+
parent.find('a.clear-button').css('display', 'block');
|
203
|
+
advance();
|
204
|
+
if (find_variant()) {
|
205
|
+
toggle();
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
$(document).ready(init);
|
210
|
+
|
211
|
+
};
|