caboose-cms 0.4.151 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +8 -8
  2. data/app/assets/javascripts/caboose/admin_products.js +79 -0
  3. data/app/assets/javascripts/caboose/application.js +2 -1
  4. data/app/assets/javascripts/caboose/cart.js +168 -0
  5. data/app/assets/javascripts/caboose/checkout.js +151 -0
  6. data/app/assets/javascripts/caboose/checkout_module.js +312 -0
  7. data/app/assets/javascripts/caboose/checkout_step1.js +179 -0
  8. data/app/assets/javascripts/caboose/checkout_step2.js +39 -0
  9. data/app/assets/javascripts/caboose/checkout_step3.js +34 -0
  10. data/app/assets/javascripts/caboose/checkout_step4.js +97 -0
  11. data/app/assets/javascripts/caboose/main.js +99 -8
  12. data/app/assets/javascripts/caboose/product.js +284 -0
  13. data/app/assets/stylesheets/caboose/admin_products.css +86 -0
  14. data/app/assets/templates/caboose/cart/add_to_cart.jst.ejs +7 -0
  15. data/app/assets/templates/caboose/cart/line_items.jst.ejs +41 -0
  16. data/app/assets/templates/caboose/checkout/address.jst.ejs +53 -0
  17. data/app/assets/templates/caboose/checkout/forms/guest.jst.ejs +8 -0
  18. data/app/assets/templates/caboose/checkout/forms/register.jst.ejs +11 -0
  19. data/app/assets/templates/caboose/checkout/forms/signin.jst.ejs +7 -0
  20. data/app/assets/templates/caboose/checkout/line_items.jst.ejs +31 -0
  21. data/app/assets/templates/caboose/checkout/login.jst.ejs +21 -0
  22. data/app/assets/templates/caboose/checkout/payment.jst.ejs +5 -0
  23. data/app/assets/templates/caboose/checkout/shipping.jst.ejs +18 -0
  24. data/app/assets/templates/caboose/product/images.jst.ejs +8 -0
  25. data/app/assets/templates/caboose/product/options.jst.ejs +19 -0
  26. data/app/controllers/caboose/application_controller.rb +29 -1
  27. data/app/controllers/caboose/cart_controller.rb +52 -0
  28. data/app/controllers/caboose/categories_controller.rb +108 -0
  29. data/app/controllers/caboose/checkout_controller.rb +325 -0
  30. data/app/controllers/caboose/orders_controller.rb +439 -0
  31. data/app/controllers/caboose/product_images_controller.rb +38 -0
  32. data/app/controllers/caboose/products_controller.rb +737 -0
  33. data/app/controllers/caboose/reviews_controller.rb +15 -0
  34. data/app/controllers/caboose/variants_controller.rb +218 -0
  35. data/app/controllers/caboose/vendors_controller.rb +73 -0
  36. data/app/helpers/caboose/application_helper.rb +4 -0
  37. data/app/helpers/caboose/categories_helper.rb +82 -0
  38. data/app/helpers/caboose/checkout_helper.rb +20 -0
  39. data/app/helpers/caboose/products_helper.rb +8 -0
  40. data/app/mailers/caboose/orders_mailer.rb +30 -0
  41. data/app/models/caboose/address.rb +26 -0
  42. data/app/models/caboose/category.rb +89 -0
  43. data/app/models/caboose/category_membership.rb +10 -0
  44. data/app/models/caboose/core_plugin.rb +15 -37
  45. data/app/models/caboose/customization_membership.rb +10 -0
  46. data/app/models/caboose/discount.rb +16 -0
  47. data/app/models/caboose/line_item.rb +81 -0
  48. data/app/models/caboose/message.rb +20 -0
  49. data/app/models/caboose/order.rb +191 -0
  50. data/app/models/caboose/order_discount.rb +10 -0
  51. data/app/models/caboose/order_pdf.rb +78 -0
  52. data/app/models/caboose/payment_processors/authorizenet.rb +53 -0
  53. data/app/models/caboose/payment_processors/base.rb +39 -0
  54. data/app/models/caboose/payment_processors/payscape.rb +94 -0
  55. data/app/models/caboose/product.rb +145 -0
  56. data/app/models/caboose/product_image.rb +62 -0
  57. data/app/models/caboose/product_image_variant.rb +10 -0
  58. data/app/models/caboose/review.rb +13 -0
  59. data/app/models/caboose/schema.rb +205 -1
  60. data/app/models/caboose/search_filter.rb +191 -0
  61. data/app/models/caboose/shipping_calculator.rb +81 -0
  62. data/app/models/caboose/states.rb +61 -52
  63. data/app/models/caboose/tax_calculator.rb +23 -0
  64. data/app/models/caboose/tax_line.rb +9 -0
  65. data/app/models/caboose/variant.rb +99 -0
  66. data/app/models/caboose/vendor.rb +22 -0
  67. data/app/views/caboose/cart/index.html.erb +8 -0
  68. data/app/views/caboose/categories/admin_edit.html.erb +79 -0
  69. data/app/views/caboose/categories/admin_index.html.erb +11 -0
  70. data/app/views/caboose/categories/admin_new.html.erb +62 -0
  71. data/app/views/caboose/checkout/_address_form.html.erb +111 -0
  72. data/app/views/caboose/checkout/_billing_form.html.erb +47 -0
  73. data/app/views/caboose/checkout/_cart.html.erb +52 -0
  74. data/app/views/caboose/checkout/_confirm.html.erb +61 -0
  75. data/app/views/caboose/checkout/_order_discount.html.erb +40 -0
  76. data/app/views/caboose/checkout/_shipping_address.html.erb +10 -0
  77. data/app/views/caboose/checkout/_shipping_method.html.erb +2 -0
  78. data/app/views/caboose/checkout/_shipping_method_form.html.erb +21 -0
  79. data/app/views/caboose/checkout/billing.html.erb +11 -0
  80. data/app/views/caboose/checkout/discount.html.erb +11 -0
  81. data/app/views/caboose/checkout/empty.html.erb +2 -0
  82. data/app/views/caboose/checkout/error.html.erb +2 -0
  83. data/app/views/caboose/checkout/index.html.erb +43 -0
  84. data/app/views/caboose/checkout/login.html.erb +2 -0
  85. data/app/views/caboose/checkout/payment.html.erb +79 -0
  86. data/app/views/caboose/checkout/relay.html.erb +23 -0
  87. data/app/views/caboose/checkout/relay_old.html.erb +12 -0
  88. data/app/views/caboose/checkout/relay_postMessage.html.erb +19 -0
  89. data/app/views/caboose/checkout/shipping.html.erb +15 -0
  90. data/app/views/caboose/checkout/step_four.html.erb +93 -0
  91. data/app/views/caboose/checkout/step_one.html.erb +56 -0
  92. data/app/views/caboose/checkout/step_one_old.html.erb +13 -0
  93. data/app/views/caboose/checkout/step_three.html.erb +23 -0
  94. data/app/views/caboose/checkout/step_two.html.erb +52 -0
  95. data/app/views/caboose/checkout/step_two_old.html.erb +14 -0
  96. data/app/views/caboose/checkout/thanks.html.erb +5 -0
  97. data/app/views/caboose/orders/_admin_footer.html.erb +2 -0
  98. data/app/views/caboose/orders/_admin_header.html.erb +31 -0
  99. data/app/views/caboose/orders/_quickbooks_order.html.erb +0 -0
  100. data/app/views/caboose/orders/admin_delete_form.html.erb +21 -0
  101. data/app/views/caboose/orders/admin_edit.html.erb +271 -0
  102. data/app/views/caboose/orders/admin_index.html.erb +89 -0
  103. data/app/views/caboose/orders/admin_new.html.erb +42 -0
  104. data/app/views/caboose/orders/admin_print.html.erb +72 -0
  105. data/app/views/caboose/orders_mailer/customer_new_order.html.erb +1 -0
  106. data/app/views/caboose/orders_mailer/customer_status_updated.html.erb +1 -0
  107. data/app/views/caboose/orders_mailer/fulfillment_new_order.html.erb +1 -0
  108. data/app/views/caboose/orders_mailer/shipping_order_ready.html.erb +1 -0
  109. data/app/views/caboose/products/_admin_footer.html.erb +2 -0
  110. data/app/views/caboose/products/_admin_header.html.erb +32 -0
  111. data/app/views/caboose/products/_sort_options.html.erb +19 -0
  112. data/app/views/caboose/products/admin_add_upcs.html.erb +58 -0
  113. data/app/views/caboose/products/admin_delete_form.html.erb +21 -0
  114. data/app/views/caboose/products/admin_edit_categories.html.erb +73 -0
  115. data/app/views/caboose/products/admin_edit_category_images.html.erb +233 -0
  116. data/app/views/caboose/products/admin_edit_description.html.erb +38 -0
  117. data/app/views/caboose/products/admin_edit_general.html.erb +104 -0
  118. data/app/views/caboose/products/admin_edit_images.html.erb +236 -0
  119. data/app/views/caboose/products/admin_edit_options.html.erb +51 -0
  120. data/app/views/caboose/products/admin_edit_seo.html.erb +37 -0
  121. data/app/views/caboose/products/admin_edit_variant_columns.html.erb +75 -0
  122. data/app/views/caboose/products/admin_edit_variant_sort_order.html.erb +63 -0
  123. data/app/views/caboose/products/admin_edit_variants.html.erb +171 -0
  124. data/app/views/caboose/products/admin_edit_variants_single.html.erb +68 -0
  125. data/app/views/caboose/products/admin_group_variants.html.erb +433 -0
  126. data/app/views/caboose/products/admin_index.html.erb +95 -0
  127. data/app/views/caboose/products/admin_new.html.erb +41 -0
  128. data/app/views/caboose/products/admin_sort.html copy.erb +155 -0
  129. data/app/views/caboose/products/admin_sort.html.erb +254 -0
  130. data/app/views/caboose/products/details.html.erb +438 -0
  131. data/app/views/caboose/products/index.html.erb +46 -0
  132. data/app/views/caboose/products/not_available.html.erb +35 -0
  133. data/app/views/caboose/variants/admin_edit.html.erb +82 -0
  134. data/app/views/caboose/variants/admin_group.html.erb +184 -0
  135. data/app/views/caboose/variants/admin_new.html.erb +59 -0
  136. data/app/views/caboose/vendors/admin_edit.html.erb +24 -0
  137. data/app/views/caboose/vendors/admin_index.html.erb +30 -0
  138. data/app/views/caboose/vendors/admin_new.html.erb +34 -0
  139. data/app/views/layouts/caboose/store/_banner.html.erb +10 -0
  140. data/app/views/layouts/caboose/store/_banner2.html.erb +10 -0
  141. data/app/views/layouts/caboose/store/_footer.html.erb +55 -0
  142. data/app/views/layouts/caboose/store/_header.html.erb +69 -0
  143. data/app/views/layouts/caboose/store/_sidebar.html.erb +27 -0
  144. data/app/views/layouts/caboose/store/application.html.erb +33 -0
  145. data/app/views/layouts/caboose/store/authorize_net.erb +18 -0
  146. data/app/views/layouts/caboose/store/layout_about.html.erb +42 -0
  147. data/app/views/layouts/caboose/store/layout_blog.html.erb +159 -0
  148. data/app/views/layouts/caboose/store/layout_confirm.html.erb +85 -0
  149. data/app/views/layouts/caboose/store/layout_contact.html.erb +38 -0
  150. data/app/views/layouts/caboose/store/layout_default.html.erb +10 -0
  151. data/app/views/layouts/caboose/store/layout_detail.html.erb +114 -0
  152. data/app/views/layouts/caboose/store/layout_order.html.erb +77 -0
  153. data/app/views/layouts/caboose/store/layout_pricing.html.erb +182 -0
  154. data/app/views/layouts/caboose/store/layout_product.html.erb +110 -0
  155. data/app/views/layouts/caboose/store/layout_profile.html.erb +55 -0
  156. data/app/views/layouts/caboose/store/layout_single.html.erb +3 -0
  157. data/app/views/layouts/caboose/store/layout_testimonial.html.erb +110 -0
  158. data/app/views/layouts/caboose/store/layout_testing.html.erb +4 -0
  159. data/config/routes.rb +126 -0
  160. data/lib/caboose.rb +46 -1
  161. data/lib/caboose/engine.rb +39 -1
  162. data/lib/caboose/version.rb +1 -1
  163. data/lib/tasks/caboose.rake +12 -0
  164. metadata +151 -4
  165. data/app/assets/javascripts/caboose/admin_page_edit_content_bak.js +0 -164
  166. data/app/assets/javascripts/caboose/model/#Untitled-1# +0 -2
@@ -0,0 +1,95 @@
1
+ <h1>Products</h1>
2
+
3
+ <p style="margin: 12px 0">
4
+ <a href="/admin/vendors/new">New Vendor</a>
5
+ <span style="margin: 0 3px">|</span>
6
+ <a href="/admin/products/new">New Product</a>
7
+ <span style="margin: 0 3px">|</span>
8
+ <a href="/admin/products/sort">Sort Products</a>
9
+ </p>
10
+
11
+ <div class="media">
12
+ <aside class="right">
13
+ <form action="/admin/products" method="get" id="search_form" style="margin: 0 0 12px">
14
+ <input type="text" name="search_like" placeholder="Title or Vendor Name" value="<%= params[:search_like] %>" style="width: 350px" />
15
+ <input type="submit" value="Search" />
16
+ <input type="button" value="Clear" onclick="window.location='/admin/products'" />
17
+
18
+ <ul>
19
+ <li><p>Find products that have:</p></li>
20
+ <li><label><input name="filters[missing_prices]" type="checkbox" <%= 'checked="checked"' if params[:filters] && params[:filters][:missing_prices] %> /> Missing prices</label></li>
21
+ <li><label><input name="filters[missing_images]" type="checkbox" <%= 'checked="checked"' if params[:filters] && params[:filters][:missing_images] %> /> Missing images</label></li>
22
+ <li><label><input name="filters[no_vendor]" type="checkbox" <%= 'checked="checked"' if params[:filters] && params[:filters][:no_vendor] %> /> No vendor</label></li>
23
+ </ul>
24
+ </form>
25
+ </aside>
26
+
27
+ <section>
28
+ <% if @products.count > 0 %>
29
+ <table class='data' id='properties_table'>
30
+ <tr>
31
+ <%= raw @gen.sortable_table_headings({
32
+ 'id' => 'ID',
33
+ 'title' => 'Title',
34
+ 'vendor' => 'Vendor'
35
+ }) %>
36
+ </tr>
37
+
38
+ <% @products.each do |p| %>
39
+ <tr onclick="window.location='/admin/products/<%= p.id %>/general';">
40
+ <td><%= raw p.id %></td>
41
+ <td><%= raw p.title %></td>
42
+ <td><%= raw p.vendor.nil? ? 'Unknown' : p.vendor.name %></td>
43
+ </tr>
44
+ <% end %>
45
+ </table>
46
+
47
+ <p><%= raw @gen.generate %></p>
48
+ <% else %>
49
+ <p>There are no products right now.</p>
50
+ <% end %>
51
+ </section>
52
+ </div>
53
+
54
+ <% content_for :caboose_css do %>
55
+ <style>
56
+ ul {
57
+ list-style-type: none;
58
+ padding: 0;
59
+ }
60
+ input[type=checkbox] {
61
+ height: 16px;
62
+ left: auto !important;
63
+ margin: 0 0 12px !important;
64
+ position: relative !important;
65
+ top: auto !important;
66
+ }
67
+ label { cursor: pointer; }
68
+ label input[type=checkbox] {
69
+ height: 15px !important;
70
+ left: auto !important;
71
+ margin: 12px 0 !important;
72
+ position: relative !important;
73
+ top: auto !important;
74
+ }
75
+
76
+ .media,
77
+ .media > section { overflow: hidden; }
78
+ .media > aside {
79
+ float: left;
80
+ margin-right: 12px;
81
+ }
82
+ .media > aside.right {
83
+ float: right;
84
+ margin-left: 12px;
85
+ }
86
+ </style>
87
+ <% end %>
88
+
89
+ <% content_for :caboose_js do %>
90
+ <script type='text/javascript'>
91
+ $(document).ready(function() {
92
+ var modal = new CabooseModal(800);
93
+ });
94
+ </script>
95
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <%
2
+ p = @product
3
+ %>
4
+ <h1>New Product</h1>
5
+
6
+ <form action='/admin/products' method='post' id='new_form'>
7
+ <input type='hidden' name='authenticity_token' value="<%= form_authenticity_token %>" />
8
+ <p><input type='text' name='title' value='' placeholder='Product Title' style='width: 400px;' /></p>
9
+ <div id='message'><%= raw flash[:message] ? flash[:message] : "" %></div>
10
+ <p>
11
+ <input type='button' value='< Back' onclick="window.location='/admin/products';" />
12
+ <input type='submit' value='Add Product' onclick='add_product(); return false' />
13
+ </p>
14
+ </form>
15
+
16
+ <% content_for :caboose_js do %>
17
+ <%= javascript_include_tag "caboose/model/all" %>
18
+ <script type='text/javascript'>
19
+
20
+ function add_product()
21
+ {
22
+ modal.autosize("<p class='loading'>Adding product...</p>");
23
+ $.ajax({
24
+ url: '/admin/products',
25
+ type: 'post',
26
+ data: $('#new_form').serialize(),
27
+ success: function(resp) {
28
+ if (resp.error)
29
+ modal.autosize("<p class='note error'>" + resp.error + "</p>");
30
+ if (resp.redirect)
31
+ window.location = resp.redirect
32
+ }
33
+ });
34
+ }
35
+
36
+ var modal = false;
37
+ $(window).load(function() {
38
+ modal = new CabooseModal(800);
39
+ });
40
+ </script>
41
+ <% end %>
@@ -0,0 +1,155 @@
1
+ <h1>Sort Products</h1>
2
+
3
+ <section>
4
+ <aside>
5
+ <ul id="source" class="sortable">
6
+ <% @products.each do |product| %>
7
+ <li id="<%= product.id %>"><%= product.title %></li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <!-- <input name="filter" type="button" value="Filter" />
12
+ <input name="clear" type="button" value="Clear" /><br /> -->
13
+ <input name="search" type="search" placeholder="Search for Products" />
14
+
15
+ <select class="filter" name="vendor">
16
+ <option value="">-- Filter by Vendor --</option>
17
+
18
+ <% @vendors.each do |vendor| %>
19
+ <option value="<%= vendor.id %>"><%= vendor.name %></option>
20
+ <% end %>
21
+ </select>
22
+ </aside>
23
+
24
+ <article>
25
+ <ul id="destination" class="sortable">
26
+ <% @products.each do |product| %>
27
+ <li id="<%= product.id %>"><%= product.title %></li>
28
+ <% end %>
29
+ </ul>
30
+
31
+ <input id="submit" type="button" value="Apply" /><br />
32
+ </article>
33
+ </section>
34
+
35
+ <% content_for :caboose_js do %>
36
+ <%= javascript_include_tag('underscore') %>
37
+ <%= javascript_include_tag('caboose/jquery-ui') %>
38
+ <%= javascript_include_tag('caboose/jquery-ui-multisortable') %>
39
+
40
+ <script>
41
+
42
+ window.products = <%= @products.to_json.html_safe %>;
43
+ window.results = window.products; // Used to store filtered results
44
+
45
+ // Delay method
46
+ var delay = (function(){
47
+ var timer = 0;
48
+ return function(callback, ms){
49
+ clearTimeout (timer);
50
+ timer = setTimeout(callback, ms);
51
+ };
52
+ })();
53
+
54
+ // Filter
55
+ function filter(event) {
56
+ var search_string = $('input[name=search]').val()
57
+ , vendor_id = $('select[name=vendor]').val();
58
+
59
+ window.results = _.filter(window.products, function(product) {
60
+ return search_string && product.title.toUpperCase().indexOf(search_string.toUpperCase()) > -1 || vendor_id && product.vendor_id == vendor_id
61
+ });
62
+
63
+ console.log(window.results);
64
+ }
65
+
66
+ $(document).ready(function() {
67
+
68
+ // Define lists
69
+ var $lists = $('ul.sortable')
70
+ , $source = $('ul#source')
71
+ , $destination = $('ul#destination');
72
+
73
+ $source.multisortable({
74
+ start: function(event, ui) {
75
+
76
+ },
77
+
78
+ connectWith: $destination
79
+ });
80
+
81
+ $destination.multisortable({
82
+ receive: function(event, ui) {
83
+ // $source.sortable('cancel');
84
+ }
85
+ });
86
+
87
+ // Filter events
88
+ $('#filter').on('click', filter);
89
+ $('select').on('change', filter);
90
+
91
+ $('input[type=search]').on('keyup', function(event) {
92
+ delay(function() { filter(event) }, 1000);
93
+ });
94
+ });
95
+ </script>
96
+ <% end %>
97
+
98
+ <% content_for :caboose_css do %>
99
+ <style>
100
+
101
+ /* Layout */
102
+
103
+ section,
104
+ section article { overflow: hidden; }
105
+
106
+ section aside {
107
+ float: left;
108
+ margin-right: 12px;
109
+ }
110
+
111
+ /* Filters */
112
+
113
+ input[type=search], select {
114
+ box-sizing: border-box;
115
+ margin: 6px 0;
116
+ width: 350px;
117
+ outline: none !important;
118
+ }
119
+ input[type=search] { height: 42px; }
120
+ select { display: block; }
121
+
122
+ input[type=button] {
123
+ background: #ccc;
124
+ cursor: pointer;
125
+ margin: 12px 0;
126
+ padding: 6px;
127
+ outline: none !important;
128
+ }
129
+
130
+ /* Lists */
131
+
132
+ ul.sortable {
133
+ border: 1px solid #ccc;
134
+ height: 250px;
135
+ list-style: none;
136
+ margin: 0;
137
+ overflow: scroll;
138
+ padding: 0;
139
+ width: 350px;
140
+ }
141
+ ul.sortable li {
142
+ box-sizing: border-box;
143
+ cursor: pointer;
144
+ cursor: grab;
145
+ overflow: hidden;
146
+ padding: 6px;
147
+ white-space: nowrap;
148
+ width: 100%;
149
+ }
150
+ ul.sortable li.selected {
151
+ background: #3e9aff;
152
+ color: #fff;
153
+ }
154
+ </style>
155
+ <% end %>
@@ -0,0 +1,254 @@
1
+ <h1>Sort Products</h1>
2
+
3
+ <section>
4
+ <aside>
5
+ <ul id="source" class="sortable">
6
+ <% @products.each do |product| %>
7
+ <li data-id="<%= product.id %>"><%= product.title %></li>
8
+ <% end %>
9
+ </ul>
10
+
11
+ <input name="search" type="search" placeholder="Search for Products" />
12
+
13
+ <select class="filter" name="vendor">
14
+ <option value="">-- Filter by Vendor --</option>
15
+
16
+ <% @vendors.each do |vendor| %>
17
+ <option value="<%= vendor.id %>"><%= vendor.name %></option>
18
+ <% end %>
19
+ </select>
20
+ </aside>
21
+
22
+ <article>
23
+ <ul id="target" class="sortable">
24
+ <% @products.each do |product| %>
25
+ <li data-id="<%= product.id %>"><%= product.title %></li>
26
+ <% end %>
27
+ </ul>
28
+
29
+ <input id="submit" type="button" value="Apply" /><br />
30
+ </article>
31
+ </section>
32
+
33
+ <% content_for :caboose_js do %>
34
+ <%= javascript_include_tag('underscore') %>
35
+ <%= javascript_include_tag('caboose/jquery-ui') %>
36
+
37
+ <script>
38
+
39
+ window.products = <%= @products.to_json.html_safe %>;
40
+ window.results = window.products; // Used to store filtered results
41
+
42
+ // Delay method
43
+ var delay = (function(){
44
+ var timer = 0;
45
+ return function(callback, ms){
46
+ clearTimeout (timer);
47
+ timer = setTimeout(callback, ms);
48
+ };
49
+ })();
50
+
51
+ // Filter
52
+ function filter(event) {
53
+ var $source = $('ul#source')
54
+ , $search = $('input[name=search]')
55
+ , search_string = $search.val() === $search.attr('placeholder') ? undefined : $search.val().trim().toUpperCase()
56
+ , vendor_id = $('select[name=vendor]').val();
57
+
58
+ $source.empty();
59
+
60
+ _.each(window.products, function(product) {
61
+ if (!search_string && !vendor_id
62
+ || search_string && product.title.toUpperCase().indexOf(search_string) > -1
63
+ || vendor_id && product.vendor_id == vendor_id) {
64
+ $source.append( $('<li/>').attr('data-id', product.id).text(product.title) );
65
+ }
66
+ });
67
+ }
68
+
69
+ function update(event) {
70
+
71
+ // Send the ordered array to the server to make it official
72
+ $.ajax({
73
+ type: 'put',
74
+ url: '/admin/products/update-sort-order',
75
+ data: { product_ids: $('ul#target').sortable('toArray', { attribute: 'data-id' }) }
76
+ });
77
+ }
78
+
79
+ $(document).ready(function() {
80
+ var selected_class = 'selected';
81
+
82
+ // Selection rules
83
+ $('ul.sortable').on('click', 'li', function(event) {
84
+ $element = $(this);
85
+
86
+ if (event.shiftKey && $last_element) {
87
+ if ($element.index() < $last_element.index()) {
88
+ $element.nextUntil($last_element).addClass(selected_class);
89
+ } else {
90
+ $element.prevUntil($last_element).addClass(selected_class);
91
+ }
92
+
93
+ $element.toggleClass(selected_class);
94
+ } else if (event.ctrlKey || event.metaKey) {
95
+ if ($element.hasClass(selected_class)) {
96
+ $element.removeClass(selected_class);
97
+ } else {
98
+ $element.addClass(selected_class);
99
+ }
100
+ } else {
101
+ $element.addClass(selected_class).siblings().removeClass(selected_class);
102
+ }
103
+
104
+ $last_element = $element;
105
+ });
106
+
107
+ // Setup sortable lists
108
+ $('ul#source').sortable({
109
+ delay: 150,
110
+ revert: 0,
111
+ connectWith: 'ul#target',
112
+
113
+ helper: function(event, item) {
114
+
115
+ // If the user just clicked and immediately drug an element
116
+ if ( !item.hasClass(selected_class) ) item.addClass(selected_class).siblings().removeClass(selected_class);
117
+
118
+ // Grab the selected elements and create the helper
119
+ var elements = item.parent().children('.' + selected_class).clone()
120
+ , helper = $('<li/>');
121
+
122
+ // Add the elements to the item multidrag data attribute
123
+ item.data('multidrag', elements).siblings('.' + selected_class);
124
+
125
+ // Pass back the helper
126
+ return helper.append(elements);
127
+ },
128
+
129
+ start: function(event, ui) {
130
+
131
+ // Make sure the item stays visible on #source
132
+ $(ui.item).show();
133
+
134
+ // Clone the original item and note the previous one, to reappend after jquery ui takes it away
135
+ item_clone = $(ui.item).clone();
136
+ previous_item = $(ui.item).prev();
137
+
138
+ // Remove selected status from ul#target
139
+ $('ul#target li.selected').removeClass('selected');
140
+ },
141
+
142
+ beforeStop: function(event, ui) {
143
+
144
+ if ($(ui.item).parent().attr('id') != 'source') {
145
+
146
+ // Remove duplicate items from old positions
147
+ ui.item.data('multidrag').each(function(index, element) {
148
+ var $element = $(element);
149
+ $('ul#target li[data-id=' + $element.attr('data-id') + ']').not('.selected').remove()
150
+ });
151
+ }
152
+ },
153
+
154
+ stop: function(event, ui) {
155
+
156
+ if ($(ui.item).parent().attr('id') == 'source') return false;
157
+
158
+ // Clear multidrag data
159
+ ui.item.after( ui.item.data('multidrag') ).remove();
160
+
161
+ // If the previous item is defined append the clone just after, otherwise prepend to #source
162
+ if (previous_item.length) {
163
+ previous_item.after(item_clone);
164
+ } else {
165
+ $('ul#source').prepend(item_clone);
166
+ }
167
+
168
+ // Update sort order
169
+ update(event, ui);
170
+ }
171
+ });
172
+
173
+ // Update sort order when done sorting
174
+ $('ul#target').sortable({ stop: update });
175
+
176
+ // Filter when the vendor select box change
177
+ $('select').on('change', filter);
178
+
179
+ // Filter, after a delay, when there is a keyup event in the search
180
+ $('input[type=search]').on('keyup', function(event) {
181
+ delay(function() { filter(event) }, 600);
182
+ });
183
+ });
184
+ </script>
185
+ <% end %>
186
+
187
+ <% content_for :caboose_css do %>
188
+ <style>
189
+
190
+ /* Layout */
191
+
192
+ section,
193
+ section article { overflow: hidden; }
194
+
195
+ section aside {
196
+ float: left;
197
+ margin-right: 12px;
198
+ }
199
+
200
+ /* Filters */
201
+
202
+ input[type=search], select {
203
+ box-sizing: border-box;
204
+ margin: 6px 0;
205
+ width: 350px;
206
+ outline: none !important;
207
+ }
208
+ input[type=search] { height: 42px; }
209
+ select { display: block; }
210
+
211
+ input[type=button] {
212
+ background: #ccc;
213
+ cursor: pointer;
214
+ margin: 12px 0;
215
+ padding: 6px;
216
+ outline: none !important;
217
+ }
218
+
219
+ /* Lists */
220
+
221
+ ul.sortable {
222
+ border: 1px solid #ccc;
223
+ height: 250px;
224
+ list-style: none;
225
+ margin: 0;
226
+ overflow-x: hidden;
227
+ overflow-y: scroll;
228
+ padding: 0;
229
+ width: 350px;
230
+ -webkit-touch-callout: none;
231
+ -webkit-user-select: none;
232
+ -khtml-user-select: none;
233
+ -moz-user-select: none;
234
+ -ms-user-select: none;
235
+ user-select: none;
236
+ }
237
+ ul.sortable li {
238
+ box-sizing: border-box;
239
+ border-bottom: 1px solid #ccc;
240
+ cursor: pointer;
241
+ cursor: grab;
242
+ display: block;
243
+ padding: 6px;
244
+ width: 100%;
245
+ }
246
+ ul.sortable li.selected {
247
+ background: #3e9aff;
248
+ border-color: #fff;
249
+ color: #fff;
250
+ }
251
+
252
+ ul#source .ui-sortable-placeholder { display: none; }
253
+ </style>
254
+ <% end %>