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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -23
  3. data/app/assets/javascripts/para/stall.coffee +1 -0
  4. data/app/assets/javascripts/para/stall/inputs/variant-select.coffee +62 -0
  5. data/app/assets/javascripts/para/stall/inputs/variants-matrix.coffee +12 -0
  6. data/app/assets/javascripts/para/stall/inputs/variants-matrix/helpers.coffee +40 -0
  7. data/app/assets/javascripts/para/stall/inputs/variants-matrix/input.coffee +133 -0
  8. data/app/assets/javascripts/para/stall/inputs/variants-matrix/nested-fields.coffee +38 -0
  9. data/app/assets/javascripts/para/stall/inputs/variants-matrix/properties_select.coffee +45 -0
  10. data/app/assets/javascripts/para/stall/inputs/variants-matrix/variant.coffee +59 -0
  11. data/app/assets/javascripts/stall.coffee +1 -0
  12. data/app/assets/javascripts/stall/add-to-cart-form.coffee +53 -28
  13. data/app/assets/javascripts/stall/cart-form.coffee +7 -2
  14. data/app/assets/stylesheets/para/stall.sass +28 -0
  15. data/app/controllers/para/stall/admin/carts_controller.rb +27 -0
  16. data/app/controllers/stall/cart_credits_controller.rb +27 -0
  17. data/app/controllers/stall/carts_controller.rb +1 -1
  18. data/app/controllers/stall/checkout/steps_controller.rb +22 -12
  19. data/app/controllers/stall/checkouts_controller.rb +1 -0
  20. data/app/controllers/stall/line_items_controller.rb +1 -0
  21. data/app/controllers/stall/payments_controller.rb +16 -1
  22. data/app/helpers/stall/credit_notes_helper.rb +25 -0
  23. data/app/helpers/stall/customers_helper.rb +3 -1
  24. data/app/models/billing_address.rb +2 -0
  25. data/app/models/cart_credit_note_adjustment.rb +3 -0
  26. data/app/models/credit_note.rb +3 -0
  27. data/app/models/credit_note_adjustment.rb +3 -0
  28. data/app/models/credit_note_usage.rb +3 -0
  29. data/app/models/product.rb +3 -0
  30. data/app/models/product_category.rb +3 -0
  31. data/app/models/product_detail.rb +3 -0
  32. data/app/models/property.rb +3 -0
  33. data/app/models/property_value.rb +3 -0
  34. data/app/models/shipping_address.rb +2 -0
  35. data/app/models/stall/models/address.rb +2 -2
  36. data/app/models/stall/models/cart.rb +2 -15
  37. data/app/models/stall/models/cart_credit_note_adjustment.rb +11 -0
  38. data/app/models/stall/models/credit_note.rb +50 -0
  39. data/app/models/stall/models/credit_note_adjustment.rb +13 -0
  40. data/app/models/stall/models/credit_note_usage.rb +16 -0
  41. data/app/models/stall/models/customer.rb +20 -8
  42. data/app/models/stall/models/line_item.rb +9 -0
  43. data/app/models/stall/models/product.rb +45 -0
  44. data/app/models/stall/models/product_category.rb +31 -0
  45. data/app/models/stall/models/product_detail.rb +17 -0
  46. data/app/models/stall/models/product_list.rb +9 -45
  47. data/app/models/stall/models/property.rb +20 -0
  48. data/app/models/stall/models/property_value.rb +23 -0
  49. data/app/models/stall/models/variant.rb +34 -0
  50. data/app/models/stall/models/variant_property_value.rb +18 -0
  51. data/app/models/variant.rb +3 -0
  52. data/app/models/variant_property_value.rb +3 -0
  53. data/app/services/stall/cart_credit_note_creation_service.rb +40 -0
  54. data/app/services/stall/cart_payment_validation_service.rb +28 -0
  55. data/app/services/stall/cart_update_service.rb +17 -6
  56. data/app/services/stall/credit_usage_service.rb +102 -0
  57. data/app/services/stall/payment_notification_service.rb +4 -8
  58. data/app/services/stall/product_list_staleness_handling_service.rb +33 -0
  59. data/app/views/admin/addresses/_fields.html.haml +9 -0
  60. data/app/views/admin/carts/_filters.html.haml +17 -0
  61. data/app/views/admin/carts/_form.html.haml +43 -0
  62. data/app/views/admin/carts/_table.html.haml +17 -0
  63. data/app/views/admin/customers/_fields.html.haml +1 -0
  64. data/app/views/admin/line_items/_fields.html.haml +15 -0
  65. data/app/views/admin/products/_table.html.haml +12 -0
  66. data/app/views/admin/properties/_form.html.haml +6 -0
  67. data/app/views/admin/properties/_table.html.haml +8 -0
  68. data/app/views/admin/property_values/_fields.html.haml +1 -0
  69. data/app/views/admin/shipments/_fields.html.haml +7 -0
  70. data/app/views/checkout/steps/_informations.html.haml +3 -1
  71. data/app/views/checkout/steps/_payment.html.haml +2 -0
  72. data/app/views/checkout/steps/_payment_return.html.haml +0 -1
  73. data/app/views/para/admin/resources/_variant_row.html.haml +24 -0
  74. data/app/views/para/stall/inputs/_variant_select.html.haml +14 -0
  75. data/app/views/para/stall/inputs/_variants_matrix.html.haml +41 -0
  76. data/app/views/stall/addresses/_fields.html.haml +6 -12
  77. data/app/views/stall/carts/_cart.html.haml +45 -37
  78. data/app/views/stall/carts/_widget.html.haml +28 -0
  79. data/app/views/stall/carts/show.html.haml +2 -0
  80. data/app/views/stall/checkout/steps/_navigation.html.haml +13 -0
  81. data/app/views/stall/credit_note_adjustments/_form.html.haml +28 -0
  82. data/app/views/stall/line_items/_added.html.haml +2 -2
  83. data/app/views/stall/line_items/_form.html.haml +1 -1
  84. data/app/views/stall/payments/manual_payment_gateway/_form.html.haml +10 -0
  85. data/app/views/stall/shared/mailers/_cart.html.haml +1 -1
  86. data/config/locales/stall.fr.yml +82 -2
  87. data/db/migrate/20161129101956_add_type_to_stall_address_ownerships.rb +52 -0
  88. data/db/migrate/20161202080218_add_reference_to_product_lists.rb +17 -0
  89. data/db/migrate/20170118103916_create_credit_notes.rb +17 -0
  90. data/db/migrate/20170118144047_create_credit_note_adjustments.rb +13 -0
  91. data/db/migrate/20170123123115_create_stall_product_categories.rb +12 -0
  92. data/db/migrate/20170123123326_create_stall_products.rb +17 -0
  93. data/db/migrate/20170123125030_create_stall_variants.rb +13 -0
  94. data/db/migrate/20170123131748_create_stall_product_category_hierarchies.rb +16 -0
  95. data/db/migrate/20170123143704_create_stall_product_details.rb +14 -0
  96. data/db/migrate/20170125152622_convert_all_money_fields_to_decimal_to_use_infinite_precision.rb +27 -0
  97. data/db/migrate/20170131162537_add_data_to_stall_adjustments.rb +5 -0
  98. data/db/migrate/20170202165514_create_stall_properties.rb +9 -0
  99. data/db/migrate/20170202165516_create_stall_property_values.rb +13 -0
  100. data/db/migrate/20170202165518_create_stall_variant_property_values.rb +13 -0
  101. data/lib/generators/stall/install/templates/initializer.rb +21 -0
  102. data/lib/generators/stall/view/view_generator.rb +41 -19
  103. data/lib/para/stall.rb +32 -0
  104. data/lib/para/stall/inputs.rb +13 -0
  105. data/lib/para/stall/inputs/variant_input_helper.rb +34 -0
  106. data/lib/para/stall/inputs/variant_select_input.rb +79 -0
  107. data/lib/para/stall/inputs/variants_matrix_input.rb +72 -0
  108. data/lib/para/stall/routes.rb +11 -0
  109. data/lib/para/stall/variants_property_config.rb +78 -0
  110. data/lib/stall.rb +10 -0
  111. data/lib/stall/addressable.rb +11 -59
  112. data/lib/stall/addresses.rb +1 -0
  113. data/lib/stall/addresses/copier_base.rb +3 -1
  114. data/lib/stall/addresses/copy.rb +10 -0
  115. data/lib/stall/addresses/copy_source_to_target.rb +10 -24
  116. data/lib/stall/addresses/prefill_target_from_source.rb +8 -16
  117. data/lib/stall/adjustable.rb +20 -0
  118. data/lib/stall/archived_paid_cart_helper.rb +36 -0
  119. data/lib/stall/cart_helper.rb +15 -7
  120. data/lib/stall/checkout/informations_checkout_step.rb +47 -50
  121. data/lib/stall/checkout/payment_return_checkout_step.rb +4 -1
  122. data/lib/stall/checkout/step.rb +24 -3
  123. data/lib/stall/checkout/step_form.rb +11 -5
  124. data/lib/stall/checkout/wizard.rb +7 -6
  125. data/lib/stall/config.rb +9 -0
  126. data/lib/stall/default_currency_manager.rb +27 -0
  127. data/lib/stall/engine.rb +14 -3
  128. data/lib/stall/payments.rb +2 -0
  129. data/lib/stall/payments/gateway_request.rb +15 -0
  130. data/lib/stall/payments/gateway_response.rb +33 -0
  131. data/lib/stall/payments/manual_payment_gateway.rb +86 -0
  132. data/lib/stall/priceable.rb +4 -0
  133. data/lib/stall/reference_manager.rb +17 -0
  134. data/lib/stall/routes.rb +1 -0
  135. data/lib/stall/shippable.rb +18 -0
  136. data/lib/stall/total_prices_manager.rb +40 -0
  137. data/lib/stall/version.rb +1 -1
  138. metadata +120 -5
  139. data/app/models/address_ownership.rb +0 -3
  140. 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: 282c715e0c37f5b8581b410bcc70a30777973284
4
- data.tar.gz: cc5283aa96ce4dd6cbb89678fc1ef73a812be26d
3
+ metadata.gz: 98069c4e8cde848ade9836933bf11f176aae37b5
4
+ data.tar.gz: baf0ab5fd49de75964401f8b01003c03446e4663
5
5
  SHA512:
6
- metadata.gz: 256db2643c7db87598b7c55f6101b4c88b180ce7b27b17cb6d2596230cd36077b48265ef0b07e7b1c7cd8a5c45ce0addc58b5198fce5768075086c20bd02c6d7
7
- data.tar.gz: 1cb10a57017e254f1fcabdf2e57e613fda6b1aee3f0392a74e0d7d7932eb4062554eae49e2100dc29f68940365da710b82192d1da67fe7322519cbd2af1e61f1
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
- [![Code Climate](https://codeclimate.com/github/rails-stall/stall.svg)](https://codeclimate.com/github/rails-stall/stall)
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. [Making a model sellable](#1--Making-a-model-sellable)
44
- 2. [Configuring shop defaults](#2--Configuring-shop-defaults)
45
- 3. [Configuring the checkout flow](#3--Configuring-the-checkout-flow)
46
- 4. [Customizing views](#4--Customizing-views)
47
- 5. [Cleaning up aborted carts](#5--Cleaning-up-aborted-carts)
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. Making a model sellable
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
- For more informations see the Wiki page :
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
- ### 2. Configuring shop defaults
71
-
72
- Before running the shop, please read through the generated Stall initializer
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
- - `store_name`
79
- - `admin_email`
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
- ### 3. Configuring the checkout flow
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
- ### 4. Customizing views
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
- ### 5. Cleaning up aborted carts
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')