stall 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +60 -23
- data/app/assets/javascripts/para/stall.coffee +1 -0
- data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +62 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix.coffee +12 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix/helpers.coffee +40 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix/input.coffee +133 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix/nested-fields.coffee +38 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix/properties_select.coffee +45 -0
- data/app/assets/javascripts/para/stall/inputs/variants-matrix/variant.coffee +59 -0
- data/app/assets/javascripts/stall.coffee +1 -0
- data/app/assets/javascripts/stall/add-to-cart-form.coffee +53 -28
- data/app/assets/javascripts/stall/cart-form.coffee +7 -2
- data/app/assets/stylesheets/para/stall.sass +28 -0
- data/app/controllers/para/stall/admin/carts_controller.rb +27 -0
- data/app/controllers/stall/cart_credits_controller.rb +27 -0
- data/app/controllers/stall/carts_controller.rb +1 -1
- data/app/controllers/stall/checkout/steps_controller.rb +22 -12
- data/app/controllers/stall/checkouts_controller.rb +1 -0
- data/app/controllers/stall/line_items_controller.rb +1 -0
- data/app/controllers/stall/payments_controller.rb +16 -1
- data/app/helpers/stall/credit_notes_helper.rb +25 -0
- data/app/helpers/stall/customers_helper.rb +3 -1
- data/app/models/billing_address.rb +2 -0
- data/app/models/cart_credit_note_adjustment.rb +3 -0
- data/app/models/credit_note.rb +3 -0
- data/app/models/credit_note_adjustment.rb +3 -0
- data/app/models/credit_note_usage.rb +3 -0
- data/app/models/product.rb +3 -0
- data/app/models/product_category.rb +3 -0
- data/app/models/product_detail.rb +3 -0
- data/app/models/property.rb +3 -0
- data/app/models/property_value.rb +3 -0
- data/app/models/shipping_address.rb +2 -0
- data/app/models/stall/models/address.rb +2 -2
- data/app/models/stall/models/cart.rb +2 -15
- data/app/models/stall/models/cart_credit_note_adjustment.rb +11 -0
- data/app/models/stall/models/credit_note.rb +50 -0
- data/app/models/stall/models/credit_note_adjustment.rb +13 -0
- data/app/models/stall/models/credit_note_usage.rb +16 -0
- data/app/models/stall/models/customer.rb +20 -8
- data/app/models/stall/models/line_item.rb +9 -0
- data/app/models/stall/models/product.rb +45 -0
- data/app/models/stall/models/product_category.rb +31 -0
- data/app/models/stall/models/product_detail.rb +17 -0
- data/app/models/stall/models/product_list.rb +9 -45
- data/app/models/stall/models/property.rb +20 -0
- data/app/models/stall/models/property_value.rb +23 -0
- data/app/models/stall/models/variant.rb +34 -0
- data/app/models/stall/models/variant_property_value.rb +18 -0
- data/app/models/variant.rb +3 -0
- data/app/models/variant_property_value.rb +3 -0
- data/app/services/stall/cart_credit_note_creation_service.rb +40 -0
- data/app/services/stall/cart_payment_validation_service.rb +28 -0
- data/app/services/stall/cart_update_service.rb +17 -6
- data/app/services/stall/credit_usage_service.rb +102 -0
- data/app/services/stall/payment_notification_service.rb +4 -8
- data/app/services/stall/product_list_staleness_handling_service.rb +33 -0
- data/app/views/admin/addresses/_fields.html.haml +9 -0
- data/app/views/admin/carts/_filters.html.haml +17 -0
- data/app/views/admin/carts/_form.html.haml +43 -0
- data/app/views/admin/carts/_table.html.haml +17 -0
- data/app/views/admin/customers/_fields.html.haml +1 -0
- data/app/views/admin/line_items/_fields.html.haml +15 -0
- data/app/views/admin/products/_table.html.haml +12 -0
- data/app/views/admin/properties/_form.html.haml +6 -0
- data/app/views/admin/properties/_table.html.haml +8 -0
- data/app/views/admin/property_values/_fields.html.haml +1 -0
- data/app/views/admin/shipments/_fields.html.haml +7 -0
- data/app/views/checkout/steps/_informations.html.haml +3 -1
- data/app/views/checkout/steps/_payment.html.haml +2 -0
- data/app/views/checkout/steps/_payment_return.html.haml +0 -1
- data/app/views/para/admin/resources/_variant_row.html.haml +24 -0
- data/app/views/para/stall/inputs/_variant_select.html.haml +14 -0
- data/app/views/para/stall/inputs/_variants_matrix.html.haml +41 -0
- data/app/views/stall/addresses/_fields.html.haml +6 -12
- data/app/views/stall/carts/_cart.html.haml +45 -37
- data/app/views/stall/carts/_widget.html.haml +28 -0
- data/app/views/stall/carts/show.html.haml +2 -0
- data/app/views/stall/checkout/steps/_navigation.html.haml +13 -0
- data/app/views/stall/credit_note_adjustments/_form.html.haml +28 -0
- data/app/views/stall/line_items/_added.html.haml +2 -2
- data/app/views/stall/line_items/_form.html.haml +1 -1
- data/app/views/stall/payments/manual_payment_gateway/_form.html.haml +10 -0
- data/app/views/stall/shared/mailers/_cart.html.haml +1 -1
- data/config/locales/stall.fr.yml +82 -2
- data/db/migrate/20161129101956_add_type_to_stall_address_ownerships.rb +52 -0
- data/db/migrate/20161202080218_add_reference_to_product_lists.rb +17 -0
- data/db/migrate/20170118103916_create_credit_notes.rb +17 -0
- data/db/migrate/20170118144047_create_credit_note_adjustments.rb +13 -0
- data/db/migrate/20170123123115_create_stall_product_categories.rb +12 -0
- data/db/migrate/20170123123326_create_stall_products.rb +17 -0
- data/db/migrate/20170123125030_create_stall_variants.rb +13 -0
- data/db/migrate/20170123131748_create_stall_product_category_hierarchies.rb +16 -0
- data/db/migrate/20170123143704_create_stall_product_details.rb +14 -0
- data/db/migrate/20170125152622_convert_all_money_fields_to_decimal_to_use_infinite_precision.rb +27 -0
- data/db/migrate/20170131162537_add_data_to_stall_adjustments.rb +5 -0
- data/db/migrate/20170202165514_create_stall_properties.rb +9 -0
- data/db/migrate/20170202165516_create_stall_property_values.rb +13 -0
- data/db/migrate/20170202165518_create_stall_variant_property_values.rb +13 -0
- data/lib/generators/stall/install/templates/initializer.rb +21 -0
- data/lib/generators/stall/view/view_generator.rb +41 -19
- data/lib/para/stall.rb +32 -0
- data/lib/para/stall/inputs.rb +13 -0
- data/lib/para/stall/inputs/variant_input_helper.rb +34 -0
- data/lib/para/stall/inputs/variant_select_input.rb +79 -0
- data/lib/para/stall/inputs/variants_matrix_input.rb +72 -0
- data/lib/para/stall/routes.rb +11 -0
- data/lib/para/stall/variants_property_config.rb +78 -0
- data/lib/stall.rb +10 -0
- data/lib/stall/addressable.rb +11 -59
- data/lib/stall/addresses.rb +1 -0
- data/lib/stall/addresses/copier_base.rb +3 -1
- data/lib/stall/addresses/copy.rb +10 -0
- data/lib/stall/addresses/copy_source_to_target.rb +10 -24
- data/lib/stall/addresses/prefill_target_from_source.rb +8 -16
- data/lib/stall/adjustable.rb +20 -0
- data/lib/stall/archived_paid_cart_helper.rb +36 -0
- data/lib/stall/cart_helper.rb +15 -7
- data/lib/stall/checkout/informations_checkout_step.rb +47 -50
- data/lib/stall/checkout/payment_return_checkout_step.rb +4 -1
- data/lib/stall/checkout/step.rb +24 -3
- data/lib/stall/checkout/step_form.rb +11 -5
- data/lib/stall/checkout/wizard.rb +7 -6
- data/lib/stall/config.rb +9 -0
- data/lib/stall/default_currency_manager.rb +27 -0
- data/lib/stall/engine.rb +14 -3
- data/lib/stall/payments.rb +2 -0
- data/lib/stall/payments/gateway_request.rb +15 -0
- data/lib/stall/payments/gateway_response.rb +33 -0
- data/lib/stall/payments/manual_payment_gateway.rb +86 -0
- data/lib/stall/priceable.rb +4 -0
- data/lib/stall/reference_manager.rb +17 -0
- data/lib/stall/routes.rb +1 -0
- data/lib/stall/shippable.rb +18 -0
- data/lib/stall/total_prices_manager.rb +40 -0
- data/lib/stall/version.rb +1 -1
- metadata +120 -5
- data/app/models/address_ownership.rb +0 -3
- data/app/models/stall/models/address_ownership.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98069c4e8cde848ade9836933bf11f176aae37b5
|
4
|
+
data.tar.gz: baf0ab5fd49de75964401f8b01003c03446e4663
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2edc779f219c016fb3b1a440c1a53cd2b8c6954e09845381ae3a90e8dec033fc30761e2765523aa0e5953bc64cd2f5ecc0b3b7407a09055fcf7066e87d1c2143
|
7
|
+
data.tar.gz: d27a12120af62029f503c481f3ca07d82630a1b5504ce7c39f9b06cc9bee5642c3879573c25600262a11b38cc4c1380245372b54abeae436e47634b4a7515071
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Stall
|
2
2
|
|
3
3
|
[![Build Status](https://api.travis-ci.org/rails-stall/stall.svg?branch=master)](http://travis-ci.org/rails-stall/stall)
|
4
|
-
[![
|
4
|
+
[![Test Coverage](https://codeclimate.com/github/rails-stall/stall/badges/coverage.svg)](https://codeclimate.com/github/rails-stall/stall/coverage)
|
5
5
|
|
6
6
|
Stall is a flexible e-commerce framework for Rails with some specific concerns
|
7
7
|
in mind :
|
@@ -40,14 +40,58 @@ This will generate :
|
|
40
40
|
|
41
41
|
In the following sections, you'll find the following informations :
|
42
42
|
|
43
|
-
1. [
|
44
|
-
2. [Configuring shop
|
45
|
-
3. [
|
46
|
-
4. [
|
47
|
-
5. [
|
43
|
+
1. [Configuring shop defaults](#1-configuring-shop-defaults)
|
44
|
+
2. [Configuring shop users](#2-configuring-shop-users)
|
45
|
+
3. [Making a model sellable](#3-making-a-model-sellable)
|
46
|
+
4. [Configuring the checkout flow](#4-configuring-the-checkout-flow)
|
47
|
+
5. [Customizing views](#5-customizing-views)
|
48
|
+
6. [Cleaning up aborted carts](#6-cleaning-up-aborted-carts)
|
48
49
|
|
49
50
|
|
50
|
-
### 1.
|
51
|
+
### 1. Configuring shop defaults
|
52
|
+
|
53
|
+
Before running the shop, please read through the generated Stall initializer
|
54
|
+
file at `config/initializers/stall.rb` and customize the default values to fit
|
55
|
+
your desired shop behavior.
|
56
|
+
|
57
|
+
Here are the mandatory ones :
|
58
|
+
|
59
|
+
- `store_name`
|
60
|
+
- `admin_email`
|
61
|
+
- `sender_email`
|
62
|
+
- `default_app_domain`
|
63
|
+
|
64
|
+
### 2. Configuring shop users
|
65
|
+
|
66
|
+
The default cart behavior admits that you have a user model, named `User` and
|
67
|
+
that you are using [Devise](https://github.com/plataformatec/devise)
|
68
|
+
compatible helpers in your controllers (`current_user`).
|
69
|
+
|
70
|
+
You can configure those settings by setting the following initializer config
|
71
|
+
parameters :
|
72
|
+
|
73
|
+
- `default_user_model_name`
|
74
|
+
- `default_user_helper_method`
|
75
|
+
|
76
|
+
Also, the user should include an inverse `:customer` relation targeting the
|
77
|
+
Stall's customer model like the following :
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
has_one :customer, as: :user
|
81
|
+
```
|
82
|
+
|
83
|
+
#### Remove user management
|
84
|
+
|
85
|
+
If you don't want to associate a user account to your customers, you'll need
|
86
|
+
to set those variables to `nil` and remove the user creation form in the
|
87
|
+
informations checkout step (See
|
88
|
+
[Configuring the checkout flow](#4-configuring-the-checkout-flow) and
|
89
|
+
[Customizing views](#5-customizing-views) for more informations on how to
|
90
|
+
do this).
|
91
|
+
|
92
|
+
Note that this is untested for now, but should be doable quite easily.
|
93
|
+
|
94
|
+
### 3. Making a model sellable
|
51
95
|
|
52
96
|
Stall allows you to make any model sellable by including the `Stall::Sellable`
|
53
97
|
mixin into your model :
|
@@ -64,24 +108,17 @@ You can now add the "Add to cart" button to your templates :
|
|
64
108
|
= add_to_cart_form_for(@book)
|
65
109
|
```
|
66
110
|
|
67
|
-
|
68
|
-
[Allowing customers to add products to cart](https://github.com/rails-stall/stall/wiki/Allowing-customers-to-add-products-to-cart)
|
111
|
+
To display the cart widget in your layout, just render the cart widget partial :
|
69
112
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
file at `config/initializers/stall.rb` and customize their values to your
|
74
|
-
fir your desired shop behavior.
|
75
|
-
|
76
|
-
Here are the mandatory ones :
|
113
|
+
```ruby
|
114
|
+
= render partial: 'stall/carts/widget', locals: { cart: current_cart }
|
115
|
+
```
|
77
116
|
|
78
|
-
|
79
|
-
-
|
80
|
-
- `sender_email`
|
81
|
-
- `default_app_domain`
|
117
|
+
For more informations see the Wiki page :
|
118
|
+
[Allowing customers to add products to cart](https://github.com/rails-stall/stall/wiki/Allowing-customers-to-add-products-to-cart)
|
82
119
|
|
83
120
|
|
84
|
-
###
|
121
|
+
### 4. Configuring the checkout flow
|
85
122
|
|
86
123
|
The checkout process is completely flexible and can be overriden easily.
|
87
124
|
|
@@ -89,7 +126,7 @@ Please see the Wiki page :
|
|
89
126
|
[The checkout process](https://github.com/rails-stall/stall/wiki/The-checkout-process)
|
90
127
|
|
91
128
|
|
92
|
-
###
|
129
|
+
### 5. Customizing views
|
93
130
|
|
94
131
|
You can copy stall views to your app with the `stall:view` generator.
|
95
132
|
The less you customize the views, the more you get it to work with future
|
@@ -104,7 +141,7 @@ Example :
|
|
104
141
|
rails generate stall:view checkout/steps/_informations checkout/steps/_payment stall/carts/_cart
|
105
142
|
```
|
106
143
|
|
107
|
-
###
|
144
|
+
### 6. Cleaning up aborted carts
|
108
145
|
|
109
146
|
A cart is created for each new visit on the app. You may want to clean
|
110
147
|
aborted carts to avoid the table to grow too big.
|
@@ -0,0 +1 @@
|
|
1
|
+
#= require para/stall/inputs/variants-matrix
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class VariantSelectInput extends Vertebra.View
|
2
|
+
events:
|
3
|
+
'change [data-variant-select-property]': 'onInputChanged'
|
4
|
+
|
5
|
+
initialize: ->
|
6
|
+
@variants = @$el.data('variant-select-data')
|
7
|
+
@$properties = @$('[data-variant-select-property]')
|
8
|
+
@$foreignKey = @$('[data-variant-select-foreign-key]')
|
9
|
+
@preparePriceTarget()
|
10
|
+
@refreshSelectedVariant()
|
11
|
+
|
12
|
+
preparePriceTarget: ->
|
13
|
+
@$priceTarget = $(@$el.data('price-target'))
|
14
|
+
return unless @$priceTarget.length
|
15
|
+
@originalPrice = @$priceTarget.html()
|
16
|
+
|
17
|
+
onInputChanged: ->
|
18
|
+
@refreshSelectedVariant()
|
19
|
+
|
20
|
+
refreshSelectedVariant: ->
|
21
|
+
selectedProperties = @serializeSelectedProperties()
|
22
|
+
variant = @fetchVariantWithProperties(selectedProperties)
|
23
|
+
@updateSelectedVariantWith(variant)
|
24
|
+
|
25
|
+
serializeSelectedProperties: ->
|
26
|
+
selectedProperties = {}
|
27
|
+
|
28
|
+
@$properties.each (i, el) ->
|
29
|
+
$property = $(el)
|
30
|
+
name = $property.data('variant-select-property')
|
31
|
+
|
32
|
+
value = if $property.is('[data-preselected]')
|
33
|
+
$property.find('[data-preselected-value]').val()
|
34
|
+
else
|
35
|
+
$property.find('input:checked, select').val()
|
36
|
+
|
37
|
+
selectedProperties[name] = parseInt(value, 10)
|
38
|
+
|
39
|
+
selectedProperties
|
40
|
+
|
41
|
+
fetchVariantWithProperties: (properties) ->
|
42
|
+
for variant in @variants
|
43
|
+
matches = true
|
44
|
+
matches = (matches and variant[key] is value) for key, value of properties when properties.hasOwnProperty(key)
|
45
|
+
return variant if matches
|
46
|
+
|
47
|
+
null
|
48
|
+
|
49
|
+
updateSelectedVariantWith: (variant) ->
|
50
|
+
value = if variant then variant.id else null
|
51
|
+
@$foreignKey.val(value)
|
52
|
+
@$foreignKey.trigger('change')
|
53
|
+
@updatePriceWith(variant)
|
54
|
+
|
55
|
+
updatePriceWith: (variant) ->
|
56
|
+
return unless @$priceTarget.length
|
57
|
+
price = if variant then variant.price else @originalPrice
|
58
|
+
@$priceTarget.html(price)
|
59
|
+
|
60
|
+
$(document).on 'page:change turbolinks:load', ->
|
61
|
+
$('[data-variant-select-input]').each (i, el) ->
|
62
|
+
new VariantSelectInput(el: el)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#= require_self
|
2
|
+
#= require ./variants-matrix/helpers
|
3
|
+
#= require ./variants-matrix/nested-fields
|
4
|
+
#= require ./variants-matrix/properties_select
|
5
|
+
#= require ./variants-matrix/input
|
6
|
+
#= require ./variants-matrix/variant
|
7
|
+
|
8
|
+
@VariantsMatrix = {}
|
9
|
+
|
10
|
+
$(document).on 'page:change turbolinks:load', ->
|
11
|
+
$('[data-variants-matrix-input]').each (i, el) ->
|
12
|
+
new VariantsMatrix.Input(el: el)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Shallow clone objects
|
2
|
+
#
|
3
|
+
# Method borrowed from first snippet from :
|
4
|
+
# http://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
|
5
|
+
#
|
6
|
+
VariantsMatrix.clone = (obj) ->
|
7
|
+
return obj if null is obj or 'object' isnt typeof obj
|
8
|
+
copy = obj.constructor()
|
9
|
+
|
10
|
+
for key, value of obj when obj.hasOwnProperty(key)
|
11
|
+
copy[key] = if 'object' is typeof obj
|
12
|
+
VariantsMatrix.clone(value)
|
13
|
+
else
|
14
|
+
value
|
15
|
+
|
16
|
+
copy
|
17
|
+
|
18
|
+
VariantsMatrix.objectLength = (obj) ->
|
19
|
+
length = 0
|
20
|
+
length++ for key, _ of obj when obj.hasOwnProperty(key)
|
21
|
+
length
|
22
|
+
|
23
|
+
VariantsMatrix.objectsAreEqual = (objectA, objectB) ->
|
24
|
+
compareObjects = (a, b) ->
|
25
|
+
for key, _ of a
|
26
|
+
continue unless a.hasOwnProperty(key) or b.hasOwnProperty(key)
|
27
|
+
|
28
|
+
return true if !a and !b
|
29
|
+
return false unless a and b
|
30
|
+
|
31
|
+
keyValuesAreEqual = if 'object' is typeof a[key]
|
32
|
+
VariantsMatrix.objectsAreEqual(a[key], b[key])
|
33
|
+
else
|
34
|
+
a[key] is b[key]
|
35
|
+
|
36
|
+
return false unless keyValuesAreEqual
|
37
|
+
|
38
|
+
true
|
39
|
+
|
40
|
+
compareObjects(objectA, objectB) and compareObjects(objectB, objectA)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
class VariantsMatrix.Input extends Vertebra.View
|
2
|
+
initialize: ->
|
3
|
+
@$variantsContainer = @$('[data-variants-matrix-variants-container]')
|
4
|
+
@$variantsTableHeader = @$('[data-variants-matrix-variants-table-header]')
|
5
|
+
|
6
|
+
@propertiesSelect = new VariantsMatrix.PropertiesSelect(el: @$('[data-variants-matrix-properties-select]'))
|
7
|
+
@listenTo(@propertiesSelect, 'change', @onPropertySelectChanged)
|
8
|
+
|
9
|
+
@nestedFieldsManager = new VariantsMatrix.NestedFields(@$('[data-variants-matrix-new-row-button]'))
|
10
|
+
|
11
|
+
@buildVariants()
|
12
|
+
|
13
|
+
buildVariants: ->
|
14
|
+
existingCombinations = @buildPossibleCombinations()
|
15
|
+
|
16
|
+
@variants = for row in @$('[data-variants-matrix-variant-row]').get()
|
17
|
+
combination = @findCombinationForRow(row, existingCombinations)
|
18
|
+
variant = new VariantsMatrix.Variant(el: row, combination: combination, input: this)
|
19
|
+
@listenTo(variant, 'destroy', @onVariantDestroyed)
|
20
|
+
|
21
|
+
variant
|
22
|
+
|
23
|
+
findCombinationForRow: (row, existingCombinations) ->
|
24
|
+
propertyValues = for propertyCell in $(row).find('[data-variants-matrix-variant-property]').get()
|
25
|
+
$property = $(propertyCell)
|
26
|
+
propertyId = $property.data('variants-matrix-variant-property')
|
27
|
+
valueId = $property.find('[data-property-value-id]').val()
|
28
|
+
{ propertyId: propertyId, id: valueId }
|
29
|
+
|
30
|
+
return combination for combination in existingCombinations when @combinationMatches(propertyValues, combination)
|
31
|
+
|
32
|
+
combinationMatches: (propertyValues, combination) ->
|
33
|
+
for combinationProperty in combination
|
34
|
+
propertyValue = value for value in propertyValues when value.propertyId is combinationProperty.propertyId
|
35
|
+
combinationValueMatches = combinationProperty and combinationProperty.id is propertyValue.id
|
36
|
+
|
37
|
+
return false unless combinationValueMatches
|
38
|
+
|
39
|
+
# Return true if all property values where found in combination
|
40
|
+
true
|
41
|
+
|
42
|
+
|
43
|
+
onPropertySelectChanged: (e) ->
|
44
|
+
@refreshAvailableProperties()
|
45
|
+
@refreshAvailableVariants()
|
46
|
+
|
47
|
+
refreshAvailableProperties: ->
|
48
|
+
propertyHeaderCells = for property in @propertiesSelect.getSelectedProperties() when property.active
|
49
|
+
$('<th/>', 'data-variants-matrix-variant-property-cell': '').text(property.name)
|
50
|
+
|
51
|
+
@$variantsTableHeader.find('[data-variants-matrix-variant-property-cell]').remove()
|
52
|
+
$enabledCell = @$variantsTableHeader.find('[data-variants-matrix-variant-enabled-cell]')
|
53
|
+
$enabledCell.after(propertyHeaderCells)
|
54
|
+
|
55
|
+
refreshAvailableVariants: ->
|
56
|
+
combinations = @buildPossibleCombinations()
|
57
|
+
@removeVariantsNotIn(combinations)
|
58
|
+
@createVariantsIn(combinations)
|
59
|
+
|
60
|
+
removeVariantsNotIn: (combinations) ->
|
61
|
+
removeableVariants = []
|
62
|
+
|
63
|
+
for variant, index in @variants
|
64
|
+
continue unless variant
|
65
|
+
matches = false
|
66
|
+
matches = true for combination in combinations when variant.matches(combination)
|
67
|
+
removeableVariants.push(variant) unless matches
|
68
|
+
|
69
|
+
# We remove variants after iterating, avoiding to change array indexes
|
70
|
+
# while we loop through it to match removeable variants
|
71
|
+
variant.remove() for variant in removeableVariants
|
72
|
+
|
73
|
+
createVariantsIn: (combinations) ->
|
74
|
+
for combination in combinations
|
75
|
+
existingVariant = @findVariantFor(combination)
|
76
|
+
variant = existingVariant or @createVariantFor(combination)
|
77
|
+
variant.show()
|
78
|
+
|
79
|
+
findVariantFor: (combination) ->
|
80
|
+
return variant for variant, index in @variants when variant.matches(combination)
|
81
|
+
|
82
|
+
createVariantFor: (combination) ->
|
83
|
+
variant = new VariantsMatrix.Variant(combination: combination, input: this)
|
84
|
+
variant.renderTo(@$variantsContainer)
|
85
|
+
@listenTo(variant, 'destroy', @onVariantDestroyed)
|
86
|
+
@variants.push(variant)
|
87
|
+
|
88
|
+
variant
|
89
|
+
|
90
|
+
# This methods loops through all select property options to create an array
|
91
|
+
# of possible variant combinations to pre
|
92
|
+
buildPossibleCombinations: ->
|
93
|
+
selection = @propertiesSelect.getSelectedProperties()
|
94
|
+
|
95
|
+
combinations = []
|
96
|
+
|
97
|
+
for property, index in selection
|
98
|
+
new_combinations = []
|
99
|
+
|
100
|
+
# If a property has no option selected, we go to the next iteration,
|
101
|
+
# leaving the combinations array untouched
|
102
|
+
continue unless property.values.length
|
103
|
+
|
104
|
+
# For the first property array, we just fill the combinations array with
|
105
|
+
# our property values
|
106
|
+
if combinations.length is 0
|
107
|
+
for value in property.values
|
108
|
+
combination = []
|
109
|
+
combination.push(value)
|
110
|
+
new_combinations.push(combination)
|
111
|
+
|
112
|
+
# For other property arrays, we create a new array by combining each
|
113
|
+
# existing combination with each property values of the current set
|
114
|
+
else
|
115
|
+
for item in combinations
|
116
|
+
for value in property.values
|
117
|
+
combination = VariantsMatrix.clone(item)
|
118
|
+
combination.push(value)
|
119
|
+
new_combinations.push(combination)
|
120
|
+
|
121
|
+
# When the new combinations array is created, we replace the previous
|
122
|
+
# one for next iterations to use it
|
123
|
+
combinations = new_combinations
|
124
|
+
|
125
|
+
combinations
|
126
|
+
|
127
|
+
# Cleanup destroyed variants
|
128
|
+
#
|
129
|
+
onVariantDestroyed: (destroyedVariant) ->
|
130
|
+
destroyedVariant.destroy()
|
131
|
+
# Remove variant from @variants array
|
132
|
+
@variants.splice(index, 1) for variant, index in @variants when variant?.matches(destroyedVariant.combination)
|
133
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
nestedFieldsElementCounter = 0
|
2
|
+
|
3
|
+
# Class that allows rendering new nested fields as variant matrix rows, allowing
|
4
|
+
# usual cocoon-based nested fields rendering options, and para's model-scoped
|
5
|
+
# templates rendering as `admin/<model_name>/_variants_row.html.haml`
|
6
|
+
#
|
7
|
+
# The class is borrowed from Cocoon code to allow using existing server-side
|
8
|
+
# code. We couldn't directly use Cocoon's API since there's only a DOM event
|
9
|
+
# based API and no public access to underlying methods
|
10
|
+
#
|
11
|
+
class VariantsMatrix.NestedFields
|
12
|
+
constructor: (@$button) ->
|
13
|
+
@$button.remove()
|
14
|
+
|
15
|
+
render: ->
|
16
|
+
assoc = @$button.data('association')
|
17
|
+
assocs = @$button.data('associations')
|
18
|
+
content = @$button.data('association-insertion-template')
|
19
|
+
regexpBraced = new RegExp('\\[new_' + assoc + '\\](.*?\\s)', 'g')
|
20
|
+
regexpUnderscored = new RegExp('_new_' + assoc + '_(\\w*)', 'g')
|
21
|
+
newId = @createNewId()
|
22
|
+
newContent = content.replace(regexpBraced, @newContentBraced(newId))
|
23
|
+
|
24
|
+
if newContent is content
|
25
|
+
regexpBraced = new RegExp('\\[new_' + assocs + '\\](.*?\\s)', 'g')
|
26
|
+
regexpUnderscored = new RegExp('_new_' + assocs + '_(\\w*)', 'g')
|
27
|
+
newContent = content.replace(regexpBraced, @newContentBraced(newId))
|
28
|
+
|
29
|
+
newContent.replace(regexpUnderscored, @newContentUnderscored(newId))
|
30
|
+
|
31
|
+
createNewId: ->
|
32
|
+
new Date().getTime() + nestedFieldsElementCounter++
|
33
|
+
|
34
|
+
newContentBraced: (id) ->
|
35
|
+
"[#{ id }]$1"
|
36
|
+
|
37
|
+
newContentUnderscored: (id) ->
|
38
|
+
"_#{ id }_$1"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class VariantsMatrix.PropertiesSelect extends Vertebra.View
|
2
|
+
events:
|
3
|
+
'change [data-variants-matrix-property-select]': 'onPropertySelectChanged'
|
4
|
+
'change [data-variants-matrix-property-selector-input]': 'onPropertyChanged'
|
5
|
+
'click [data-variants-matrix-property-selector-label]': 'onPropertyLabelClicked'
|
6
|
+
|
7
|
+
initialize: ->
|
8
|
+
@$propertySelects = @$('[data-variants-matrix-property-select]').selectize
|
9
|
+
plugins: ['remove_button']
|
10
|
+
|
11
|
+
# Serializes all selected properties into an array of hashes containing the
|
12
|
+
# field name and the selected property values ids
|
13
|
+
#
|
14
|
+
getSelectedProperties: ->
|
15
|
+
for el in @$propertySelects.get()
|
16
|
+
$field = $(el)
|
17
|
+
|
18
|
+
# Ensure the property is enabled
|
19
|
+
$propertyListItem = $field.closest('[data-variants-matrix-property-id]')
|
20
|
+
continue unless $propertyListItem.is(':visible')
|
21
|
+
|
22
|
+
propertyId = parseInt($propertyListItem.data('variants-matrix-property-id'), 10)
|
23
|
+
|
24
|
+
fieldName = $field.data('variants-matrix-property-select')
|
25
|
+
values = @serializeFieldValues($field, propertyId)
|
26
|
+
{ id: propertyId, name: fieldName, values: values, active: !!values.length }
|
27
|
+
|
28
|
+
serializeFieldValues: ($field, propertyId) ->
|
29
|
+
for option in $field.find(':selected').get()
|
30
|
+
$option = $(option)
|
31
|
+
{ id: $option.attr('value'), name: $option.text(), propertyId: propertyId }
|
32
|
+
|
33
|
+
onPropertyChanged: (e) ->
|
34
|
+
$checkbox = $(e.currentTarget)
|
35
|
+
isActive = $checkbox.is(':checked')
|
36
|
+
propertyId = $checkbox.val()
|
37
|
+
$target = @$("[data-variants-matrix-property-id='#{ propertyId }']")
|
38
|
+
$target.toggleClass('hidden', !isActive)
|
39
|
+
@trigger('change')
|
40
|
+
|
41
|
+
onPropertyLabelClicked: (e) ->
|
42
|
+
e.stopPropagation()
|
43
|
+
|
44
|
+
onPropertySelectChanged: (e) ->
|
45
|
+
@trigger('change')
|