stall 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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')