stall 0.2.0 → 0.3.1
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.
- 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
|
[](http://travis-ci.org/rails-stall/stall)
|
|
4
|
-
[](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')
|