caboose-cms 0.5.189 → 0.5.190

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NzgwNzYwNDY1NWVlMTZmNmEwNzQxMTM5NDk4NTljYzAwNTJkODcxYQ==
4
+ Zjk2ZjJkNGZlOWM0YmViYmY0ZTlmYmQzYmRjZmRiZjY4ZjU2MTI4NA==
5
5
  data.tar.gz: !binary |-
6
- NTk3ZTZhY2RhOGEzMWQ0Nzk5Y2Q2NzE0NTMwYjc2ZWJiODQ3Y2ZkMg==
6
+ ZTVjOWQxNGY4NDlmNmE4ZjY1ZDk3ZDk3ZTUyMWM0ZjkzYjkzY2M3OQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NzdlN2JjYTZiOGQ5Yzk2MWFlYTdmYTljM2EzMTNiMWQ3NWE0NjVmNmNmMzFm
10
- YmNjZTM4ZmY2NDdhNzRiM2M2OTE5NTA2Yzk0ZTJkZGEwM2E5YTY3YjZhMzdm
11
- NGM2YTQ5MmY5MDMxNzRkZjFlMzQwNTcwYzhkM2Q0ZjkyZjMzZDA=
9
+ ZmU3YjRhNDc4NzNhMWYyYTU5M2FmYWE3ZDVmMjg3NGM0ZDhlMTY4NWY2NTcz
10
+ N2ExNmY1NTk1YWEwMzZhZWQ0OWM5MGI4MTYwYjlmNTQwYzU1NzNiY2E3ZWE2
11
+ NjcwM2YxN2JkZmFjZDAzZjdiMTdmZDZjYzdkZjQzMjVjMDllYmI=
12
12
  data.tar.gz: !binary |-
13
- Mzg5NGI4YzQyMGJiZmM2YTE4ZTI2YWEzNTc0YTcxMzJkNzM0ZDA4Nzc3MzE4
14
- Nzk1MmQ5MDE3MDM1YWRlMDY3Y2NlZjUyZDJhZGNhZDE0OGIxN2U3ZGYzMGE4
15
- NjZlYWIwOTFmMThjYzZlZDdkZmQwMjVhYjBiN2FiMDE5NmM2MDM=
13
+ NDU2ZGYxZDcyZGZjNGQ4ZDdkYTkxNGI1YmRlNTI5YmJkODMwOWE0MDc3NGFm
14
+ OTgwMzdmZjVlNTVjOTFjZTNhNzczZjEwYTE2OTRhMDQ4ZjFmOTIyOGMwNzg5
15
+ Mjk5NWExYTViZDI3YmFkMzczYTBiZDg2MzFhZTAyOGExNmE5YjA=
@@ -266,7 +266,7 @@ Caboose.Store.Modules.Product = (function() {
266
266
  Caboose.Store.Modules.Cart.set_variant(variant);
267
267
  if (variant) self.set_image_from_variant(variant);
268
268
  if (variant && self.$price.length) self.$price.empty().text('$' + parseFloat((variant.price * 100) / 100).toFixed(2));
269
- if (self.variant_on_sale(variant)) {
269
+ if (variant && self.variant_on_sale(variant)) {
270
270
  self.$price.addClass("on-sale");
271
271
  var percent = 100 - ((variant.sale_price / variant.price) * 100).toFixed(0);
272
272
  $("#percent-off").text("SALE! Save " + percent + "%");
@@ -0,0 +1,305 @@
1
+
2
+ var ProductController = function(params) { this.init(params); };
3
+
4
+ ProductController.prototype = {
5
+
6
+ product: false,
7
+ option_values: [[], [], []],
8
+
9
+ init: function() {
10
+ self.$price = self.$product.find('#product-price');
11
+ $("<span id='percent-off'></span").insertAfter(self.$price);
12
+ $("<span id='sale-price'></span").insertBefore(self.$price);
13
+ if (!self.$product.length) return false;
14
+ },
15
+
16
+ refresh: function(after)
17
+ {
18
+ var that = this;
19
+ $.ajax({
20
+ url: '/products/' + that.product.id + '/info',
21
+ type: 'get',
22
+ success: function(resp) {
23
+ that.product = resp.product;
24
+ that.option_values = [that.option1_values, that.option2_values, that.option3_values];
25
+ that.render();
26
+ that.set_variant(self.get_initial_variant());
27
+ that.set_options_from_variant(self.variant);
28
+ }
29
+ );
30
+ },
31
+
32
+ render: function()
33
+ {
34
+ var that = this;
35
+ that.render_images();
36
+ that.render_options();
37
+ $('#message').empty();
38
+ },
39
+
40
+ initalize_zoom: function(image_url)
41
+ {
42
+ var big_image = $("#product-images").children("figure").first();
43
+ big_image.data("zoom-image",image_url);
44
+ big_image.elevateZoom();
45
+ },
46
+
47
+ render_images: function()
48
+ {
49
+ var that = this;
50
+ var div = $('<div/>').append($('<figure/>').attr('id', 'main_image').click(function(e) {
51
+ window.location = $(e.target).css('background-image').match(/^url\("(.*)"\)$/)[1];
52
+ }));
53
+
54
+ if (that.product.images.length > 0)
55
+ {
56
+ var ul = $('<ul/>');
57
+ $.each(product.images, function(i, image) {
58
+ ul.append(
59
+ $('<li/>').data('id', image.id).append(
60
+ $('<figure/>')
61
+ .data('url-large', image.urls.large)
62
+ .data('url-huge' , image.urls.huge)
63
+ .css('background-image', "url(" + image.urls.thumb + ")")
64
+ .click(function(e) {
65
+ $('#main_image').css('background-image', 'url(' + $(this).data('url-large') + ')');
66
+ that.initalize_zoom($(this).data('url-huge'));
67
+ })
68
+ )
69
+ );
70
+ });
71
+ div.append(ul);
72
+ }
73
+ else
74
+ {
75
+ div.append($('<p/>').html("This product doesn't have any images yet."));
76
+ }
77
+ $('#product-images').empty().append(div);
78
+ },
79
+
80
+ render_options: function()
81
+ {
82
+ var that = this;
83
+ var div = $('<div/>');
84
+
85
+ if (that.product.options.length > 0)
86
+ {
87
+ $.each(that.product.options, function(i, option) {
88
+ div.append($('<h3>').html(option));
89
+ var ul = $('<ul/>').attr('id', 'option' + (i + 1)).data('name', option);
90
+ $.each(that.option_values[i], function(j, option_value) {
91
+ ul.append($('<li/>')
92
+ .attr('id', 'option_' + i + '_' + j)
93
+ .data('i', i).data('j', j).data('value', option_value)
94
+ .html(option_value)
95
+ .click(function(e) {
96
+ that.select_option($(this).data('i'), $(this).data('value'));
97
+ })
98
+ );
99
+ });
100
+ div.append(ul);
101
+ });
102
+ }
103
+ else
104
+ {
105
+ div.append($('<p/>').html("This product doesn't have any options."));
106
+ }
107
+ $('#product-options').empty().append(div);
108
+ },
109
+
110
+ selected_options: false,
111
+
112
+ select_option: function(option_index, value)
113
+ {
114
+ var that = this;
115
+ that.selected_options[option_index] = (that.selected_options[option_index] == value ? false : value);
116
+ $('#product-options li').removeClass('available').removeClass('unavailable').removeClass('selected');
117
+
118
+ if (that.product.options.length > 0)
119
+ {
120
+ $.each(that.product.options, function(i, option) {
121
+ $.each(that.option_values[i], function(j, option_value) {
122
+ var el = $('#option_' + i + '_' + j);
123
+ if (that.variant_is_available(i, option_value))
124
+ {
125
+ el.addClass('available');
126
+ if (that.selected_options[i] == option_value)
127
+ el.addClass('selected');
128
+ }
129
+ else
130
+ {
131
+ el.addClass('unavailable');
132
+ }
133
+ });
134
+ });
135
+ }
136
+ //self.set_variant(self.get_variant_from_options(self.get_current_options()));
137
+ },
138
+
139
+ variant_is_available: function(option_index, option_value)
140
+ {
141
+ var that = this;
142
+ var exists = true;
143
+
144
+ var options_array = [];
145
+ $.each(that.product.options, function(i, option) {
146
+ if (that.selected_option[i] == false) // Any option will work
147
+ {
148
+ options_array[i] = that.option_values[i];
149
+ }
150
+ else if (i == option_index) // Only the given option_value will work
151
+ {
152
+ options_array[i] = [option_value];
153
+ }
154
+ else // Only the previously selected option will work
155
+ {
156
+ options_array[i] = [that.selected_option[i]];
157
+ }
158
+ });
159
+
160
+ var found_it = false;
161
+ $.each(that.product.variants, function(i, v) {
162
+ var matches = true;
163
+ $.each(options_array, function(j, option_values) {
164
+ if (option_values.index_of(v['option'+j]) != -1)
165
+ {
166
+ matches = false;
167
+ return false;
168
+ }
169
+ });
170
+ if (matches)
171
+ {
172
+ found_it = true;
173
+ return false;
174
+ }
175
+ });
176
+ return found_it;
177
+ },
178
+
179
+ variant_for_options: function()
180
+ {
181
+ var that = this;
182
+ var exists = true;
183
+
184
+ var options_array = [];
185
+ $.each(that.product.options, function(i, option) {
186
+ options_array[i] = that.selected_option[i];
187
+ });
188
+
189
+ var matching_variant = false;
190
+ $.each(that.product.variants, function(i, v) {
191
+ var matches = true;
192
+ $.each(options_array, function(j, option_value) {
193
+ if (option_value != v['option'+j]) { matches = false; return false; }
194
+ });
195
+ if (matches) { matching_variant = v; return false; }
196
+ });
197
+ return matching_variant;
198
+ },
199
+
200
+ //
201
+ // Variant Methods
202
+ //
203
+
204
+ initial_variant: function ()
205
+ {
206
+ var that = this;
207
+ var available_variants = $.grep(that.product.variants, function(v) { return v.quantity_in_stock > 0; });
208
+
209
+ if (!available_variants)
210
+ $('#add-to-cart').after($('<p/>').addClass('message error').text('Out of Stock')).remove();
211
+
212
+ variant = available_variants ? available_variants[0] : that.product.variants[0];
213
+ return variant;
214
+ },
215
+
216
+ self.set_variant = function(variant) {
217
+ self.variant = variant;
218
+ Caboose.Store.Modules.Cart.set_variant(variant);
219
+ if (variant) self.set_image_from_variant(variant);
220
+ if (variant && self.$price.length) self.$price.empty().text('$' + parseFloat((variant.price * 100) / 100).toFixed(2));
221
+ if (variant && self.variant_on_sale(variant)) {
222
+ self.$price.addClass("on-sale");
223
+ var percent = 100 - ((variant.sale_price / variant.price) * 100).toFixed(0);
224
+ $("#percent-off").text("SALE! Save " + percent + "%");
225
+ $("#sale-price").text('$' + parseFloat((variant.sale_price * 100) / 100).toFixed(2));
226
+ }
227
+ else {
228
+ self.$price.removeClass("on-sale");
229
+ $("#percent-off").text('');
230
+ $("#sale-price").text('');
231
+ }
232
+ },
233
+
234
+ variant_on_sale: function(variant)
235
+ {
236
+ if (variant.sale_price != "" && variant.sale_price != 0)
237
+ {
238
+ var d = new Date();
239
+ if (variant.date_sale_starts && d < variant.date_sale_starts) return false;
240
+ if (variant.date_sale_ends && d > variant.date_sale_ends) return false;
241
+ return true;
242
+ }
243
+ return false;
244
+ },
245
+
246
+ get_variant: function(variant_id)
247
+ {
248
+ for (var v in this.product.variants)
249
+ if (v.id == variant_id)
250
+ return v;
251
+ return false;
252
+ },
253
+
254
+ //
255
+ // Option Methods
256
+ //
257
+
258
+ self.get_option_attribute = function(option)
259
+ {
260
+ optionName = _.isObject(option) ? option.name : option;
261
+ if (self.product.option1 == optionName) { return 'option1'; }
262
+ else if (self.product.option2 == optionName) { return 'option2'; }
263
+ else if (self.product.option3 == optionName) { return 'option3'; }
264
+ },
265
+
266
+ self.get_current_options = function()
267
+ {
268
+ var options = [];
269
+
270
+ self.$options.children('ul').each(function(index, element) {
271
+ var $option = $(element);
272
+
273
+ options.push({
274
+ name: $option.data('name'),
275
+ value: $option.children('.selected').first().data('value')
276
+ });
277
+ });
278
+
279
+ return options;
280
+ },
281
+
282
+ //
283
+ // Image Methods
284
+ //
285
+
286
+ set_image_from_variant: function(variant)
287
+ {
288
+ if (!variant || !variant.images || variant.images.length == 0 || !variant.images[0]) return;
289
+
290
+ $('#main_image').css('background-image', 'url(' + $(this).data('url-large') + ')');
291
+ that.initalize_zoom($(this).data('url-huge'));
292
+
293
+ var $figure = self.$images.children('figure');
294
+ if (variant.images && variant.images.length > 0 && variant.images[0]) {
295
+ $figure.css('background-image', 'url(' + variant.images[0].urls.large + ')');
296
+ self.initalize_zoom(variant.images[0].urls.huge);
297
+ } else if ($figure.css('background-image').toLowerCase() == 'none') {
298
+ $figure.css('background-image', 'url(' + _.first(self.product.images).urls.large + ')');
299
+ self.initalize_zoom(_.first(self.product.images).urls.huge);
300
+ }
301
+
302
+ };
303
+
304
+ return self;
305
+ }).call(Caboose.Store);
@@ -0,0 +1,324 @@
1
+ //
2
+ // Product
3
+ //
4
+
5
+ Caboose.Store.Modules.Product = (function() {
6
+ var self = {
7
+ templates: {
8
+ images: JST['caboose/product/images'],
9
+ options: JST['caboose/product/options']
10
+ }
11
+ };
12
+
13
+ //
14
+ // Initialize
15
+ //
16
+
17
+ self.initialize = function() {
18
+ self.$product = $('#product');
19
+ self.$price = self.$product.find('#product-price');
20
+ $("<span id='percent-off'></span").insertAfter(self.$price);
21
+ $("<span id='sale-price'></span").insertBefore(self.$price);
22
+ if (!self.$product.length) return false;
23
+
24
+ $.get('/products/' + self.$product.data('id') + '/info', function(response) {
25
+ self.product = response.product;
26
+ self.option1_values = response.option1_values;
27
+ self.option2_values = response.option2_values;
28
+ self.option3_values = response.option3_values;
29
+ self.render();
30
+ self.bind_events();
31
+ self.set_variant(self.get_initial_variant());
32
+ self.set_options_from_variant(self.variant);
33
+ });
34
+
35
+ };
36
+
37
+ //
38
+ // Render
39
+ //
40
+ self.render = function() {
41
+ var render_functions = [];
42
+ render_functions.push(self.render_images);
43
+ render_functions.push(self.render_options);
44
+
45
+ _.each(render_functions, function(render_function, index) {
46
+ var finished = index == (render_functions.length - 1);
47
+
48
+ render_function(function() {
49
+ if (finished) self.$product.removeClass('loading');
50
+ });
51
+ });
52
+ };
53
+
54
+ self.initalize_zoom = function(image_url) {
55
+ var big_image = $("#product-images").children("figure").first();
56
+ big_image.data("zoom-image",image_url);
57
+ big_image.elevateZoom();
58
+ }
59
+
60
+ self.render_images = function(callback) {
61
+ self.$images = $('#product-images', self.$product);
62
+ if (!self.$images.length) return false;
63
+ self.$images.empty().html(self.templates.images({ images: self.product.images }));
64
+ if (callback) callback();
65
+ };
66
+
67
+ self.render_options = function(callback) {
68
+ self.$options = $('#product-options', self.$options);
69
+ if (!self.$options.length) return false;
70
+ self.$options.empty().html(self.templates.options({ options: self.get_options_with_all_values() }));
71
+ if (callback) callback();
72
+ };
73
+
74
+ //
75
+ // Out of Stock
76
+ //
77
+
78
+ self.out_of_stock = function() {
79
+ self.$product.find('#add-to-cart').after($('<p/>').addClass('message error').text('Out of Stock')).remove();
80
+ };
81
+
82
+ //
83
+ // Events
84
+ //
85
+
86
+ self.bind_events = function() {
87
+ self.$images.find('ul > li > figure').on('click', self.thumb_click_handler);
88
+ self.$images.children('figure').on('click', self.image_click_handler);
89
+ self.$options.find('ul').on('click', 'li', self.option_click_handler);
90
+ };
91
+
92
+ self.thumb_click_handler = function(event) {
93
+ self.$images.children('figure').css('background-image', 'url(' + $(event.target).data('url-large') + ')');
94
+ self.initalize_zoom($(event.target).data('url-large').replace('large','huge'));
95
+ };
96
+
97
+ self.image_click_handler = function(event) {
98
+ window.location = $(event.target).css('background-image').match(/^url\("(.*)"\)$/)[1];
99
+ };
100
+
101
+ self.option_click_handler = function(event) {
102
+ var $target_option = $(event.delegateTarget)
103
+ var $target_value = $(event.target);
104
+
105
+ if ($target_value.hasClass('selected')) {
106
+ $target_value.removeClass('selected');
107
+ $target_value = $();
108
+ } else {
109
+ $target_value.addClass('selected').siblings().removeClass('selected');
110
+
111
+ self.$options.find('ul').not($target_option).each(function(index, element) {
112
+ var $currentOption = $(element)
113
+ , $currentValue = $currentOption.children('.selected')
114
+ , $otherOption = self.$options.find('ul').not($target_option.add($currentOption))
115
+ , $otherValue = $otherOption.children('.selected')
116
+ , options = [];
117
+
118
+ if (!$currentValue.length) return true;
119
+
120
+ options.push({ name: $currentOption.data('name'), value: $currentValue.data('value') });
121
+ options.push({ name: $target_option.data('name'), value: $target_value.data('value') });
122
+
123
+ if (!!!self.get_variant_from_options(options)) {
124
+ $currentValue.removeClass('selected');
125
+ } else if ($otherOption.length && $otherValue.length) {
126
+ options.push({ name: $otherOption.data('name'), value: $otherValue.data('value') });
127
+ if (!!!self.get_variant_from_options(options)) $otherValue.removeClass('selected');
128
+ }
129
+ });
130
+
131
+ $target_option.children().each(function(index, element) {
132
+ var $currentOption = $target_option
133
+ , $currentValue = $(element)
134
+ , $otherOption = self.$options.find('ul').not($target_option).first()
135
+ , $otherValue = $otherOption.children('.selected')
136
+ , $otherOtherOption = self.$options.find('ul').not($target_option.add($otherOption))
137
+ , $otherOtherValue = $otherOtherOption.children('.selected')
138
+ , options = [];
139
+
140
+ options.push({ name: $currentOption.data('name'), value: $currentValue.data('value') });
141
+ if ($otherOption.length && $otherValue.length) options.push({ name: $otherOption.data('name'), value: $otherValue.data('value') });
142
+ if ($otherOtherOption.length && $otherOtherValue.length) options.push({ name: $otherOtherOption.data('name'), value: $otherOtherValue.data('value') });
143
+ self.toggle_option_value($currentValue, !!self.get_variant_from_options(options));
144
+ });
145
+ }
146
+
147
+ self.$options.find('ul').not($target_option).each(function(index, element) {
148
+ var $currentOption = $(element);
149
+
150
+ $currentOption.children().each(function(index, element) {
151
+ var $currentValue = $(element)
152
+ , $otherOption = self.$options.find('ul').not($target_option.add($currentOption))
153
+ , $otherValue = $otherOption.children('.selected')
154
+ , options = [];
155
+
156
+ options.push({ name: $currentOption.data('name'), value: $currentValue.data('value') });
157
+ if ($target_option.length && $target_value.length) options.push({ name: $target_option.data('name'), value: $target_value.data('value') });
158
+ if ($otherOption.length && $otherValue.length) options.push({ name: $otherOption.data('name'), value: $otherValue.data('value') });
159
+ self.toggle_option_value($currentValue, !!self.get_variant_from_options(options));
160
+ });
161
+ });
162
+
163
+ self.set_variant(self.get_variant_from_options(self.get_current_options()));
164
+ };
165
+
166
+ //
167
+ // Option Methods
168
+ //
169
+
170
+ self.get_options_from_product = function() {
171
+ return _.compact([
172
+ self.product.option1 ? self.product.option1 : undefined,
173
+ self.product.option2 ? self.product.option2 : undefined,
174
+ self.product.option3 ? self.product.option3 : undefined
175
+ ]);
176
+ };
177
+
178
+
179
+ self.get_options_from_variant = function(variant) {
180
+ return _.compact([
181
+ self.product.option1 ? { name: self.product.option1, value: variant.option1 } : undefined,
182
+ self.product.option2 ? { name: self.product.option2, value: variant.option2 } : undefined,
183
+ self.product.option3 ? { name: self.product.option3, value: variant.option3 } : undefined
184
+ ]);
185
+ };
186
+
187
+ self.get_options_with_all_values = function() {
188
+ var options = [];
189
+ if (self.product.option1) options.push({ name: self.product.option1, values: self.option1_values });
190
+ if (self.product.option2) options.push({ name: self.product.option2, values: self.option2_values });
191
+ if (self.product.option3) options.push({ name: self.product.option3, values: self.option3_values });
192
+ return options;
193
+ };
194
+
195
+ self.get_option_attribute = function(option) {
196
+ optionName = _.isObject(option) ? option.name : option;
197
+
198
+ if (self.product.option1 == optionName) {
199
+ return 'option1';
200
+ } else if (self.product.option2 == optionName) {
201
+ return 'option2';
202
+ } else if (self.product.option3 == optionName) {
203
+ return 'option3';
204
+ }
205
+ };
206
+
207
+ self.get_current_options = function() {
208
+ var options = [];
209
+
210
+ self.$options.children('ul').each(function(index, element) {
211
+ var $option = $(element);
212
+
213
+ options.push({
214
+ name: $option.data('name'),
215
+ value: $option.children('.selected').first().data('value')
216
+ });
217
+ });
218
+
219
+ return options;
220
+ };
221
+
222
+ self.toggle_option_value = function($value, on) {
223
+ if (on) {
224
+ $value.addClass('available').removeClass('unavailable');
225
+ } else {
226
+ $value.addClass('unavailable').removeClass('available selected');
227
+ }
228
+ };
229
+
230
+ //
231
+ // Variant Methods
232
+ //
233
+
234
+ self.get_initial_variant = function () {
235
+ var variant = _.find(self.product.variants, function(variant) {
236
+ return variant.quantity_in_stock > 0;
237
+ });
238
+
239
+ if (!variant) {
240
+ variant = _.first(self.product.variants);
241
+ self.out_of_stock();
242
+ }
243
+
244
+ return variant;
245
+ };
246
+
247
+ self.get_variant_from_options = function(options) {
248
+ if (_.find(options, function(option) { return option.value == undefined })) return false;
249
+
250
+ var attributes = _.object(_.map(options, function(option) {
251
+ return [self.get_option_attribute(option.name), option.value.toString()]
252
+ }));
253
+
254
+ var variants = _.sortBy(_.where(self.product.variants, attributes), function(variant) { return variant.price });
255
+ return _.find(variants, function(variant) { return variant.quantity_in_stock > 0 });
256
+ };
257
+
258
+ self.set_options_from_variant = function(variant) {
259
+ if (variant.option1) $('#option1 li[data-value="' + variant.option1 + '"]', self.$options).click();
260
+ if (variant.option1) $('#option2 li[data-value="' + variant.option2 + '"]', self.$options).click();
261
+ if (variant.option1) $('#option3 li[data-value="' + variant.option3 + '"]', self.$options).click();
262
+ };
263
+
264
+ self.set_variant = function(variant) {
265
+ self.variant = variant;
266
+ Caboose.Store.Modules.Cart.set_variant(variant);
267
+ if (variant) self.set_image_from_variant(variant);
268
+ if (variant && self.$price.length) self.$price.empty().text('$' + parseFloat((variant.price * 100) / 100).toFixed(2));
269
+ if (variant && self.variant_on_sale(variant)) {
270
+ self.$price.addClass("on-sale");
271
+ var percent = 100 - ((variant.sale_price / variant.price) * 100).toFixed(0);
272
+ $("#percent-off").text("SALE! Save " + percent + "%");
273
+ $("#sale-price").text('$' + parseFloat((variant.sale_price * 100) / 100).toFixed(2));
274
+ }
275
+ else {
276
+ self.$price.removeClass("on-sale");
277
+ $("#percent-off").text('');
278
+ $("#sale-price").text('');
279
+ }
280
+ };
281
+
282
+ self.variant_on_sale = function(variant) {
283
+ var is_on_sale = false;
284
+ if (variant.sale_price != "" && variant.sale_price != 0) {
285
+ var d = new Date();
286
+ if (variant.date_sale_starts && d < variant.date_sale_starts) {
287
+ is_on_sale = false;
288
+ }
289
+ else if (variant.date_sale_ends && d > variant.date_sale_ends) {
290
+ is_on_sale = false;
291
+ }
292
+ else {
293
+ is_on_sale = true;
294
+ }
295
+ }
296
+ return is_on_sale;
297
+ }
298
+
299
+ self.get_variant = function(id) {
300
+ return _.find(self.product.variants, function(variant) { return variant.id == (id || self.variant.id) });
301
+ };
302
+
303
+ //
304
+ // Image Methods
305
+ //
306
+
307
+ self.set_image_from_variant = function(variant) {
308
+ if (!variant || !variant.images || variant.images.length == 0 || !variant.images[0]) return;
309
+ self.$product.trigger('variant:updated');
310
+
311
+ var $figure = self.$images.children('figure');
312
+ if (variant.images && variant.images.length > 0 && variant.images[0]) {
313
+ $figure.css('background-image', 'url(' + variant.images[0].urls.large + ')');
314
+ self.initalize_zoom(variant.images[0].urls.huge);
315
+ } else if ($figure.css('background-image').toLowerCase() == 'none') {
316
+ $figure.css('background-image', 'url(' + _.first(self.product.images).urls.large + ')');
317
+ self.initalize_zoom(_.first(self.product.images).urls.huge);
318
+ }
319
+
320
+ };
321
+
322
+ return self;
323
+ }).call(Caboose.Store);
324
+
@@ -167,7 +167,7 @@ module Caboose
167
167
 
168
168
  # Find the last order for the user
169
169
  @last_order = Order.where(:customer_id => @logged_in_user.id).order("id desc").limit(1).first
170
-
170
+
171
171
  end
172
172
 
173
173
  #===========================================================================
@@ -444,8 +444,9 @@ module Caboose
444
444
  current_tags.each{ |t| PageTag.where(:page_id => page.id, :tag => t).destroy_all if !new_tags.include?(t) }
445
445
 
446
446
  # Add any new tags not in current_tags
447
- new_tags.each{ |t| PageTag.create(:page_id => page.id, :tag => t) if !current_tags.include?(t) }
447
+ new_tags.each{ |t| PageTag.create(:page_id => page.id, :tag => t) if !current_tags.include?(t) }
448
448
  end
449
+
449
450
  end
450
451
 
451
452
  resp.success = save && page.save
@@ -0,0 +1,41 @@
1
+
2
+ module Caboose
3
+ class RetargetingController < ApplicationController
4
+ layout 'caboose/admin'
5
+
6
+ def before_action
7
+ @page = Page.page_with_uri(request.host_with_port, '/admin')
8
+ end
9
+
10
+ # GET /admin/sites/:site_id/retargeting
11
+ def admin_edit
12
+ return if !user_is_allowed('sites', 'edit')
13
+ if !@site.is_master
14
+ @error = "You are not allowed to manage sites."
15
+ render :file => 'caboose/extras/error' and return
16
+ end
17
+ @site = Site.find(params[:site_id])
18
+ end
19
+
20
+ # PUT /admin/sites/:id/retargeting
21
+ def admin_update
22
+ render :json => { :error => "You are not allowed to manage sites." } and return if !user_is_allowed('sites', 'edit') || !@site.is_master
23
+
24
+ resp = StdClass.new
25
+ site = Site.find(params[:site_id])
26
+ rc = site.retargeting_config
27
+
28
+ params.each do |name,value|
29
+ case name
30
+ when 'conversion_id' then rc.conversion_id = value
31
+ when 'labels_function' then rc.labels_function = value
32
+ end
33
+ end
34
+
35
+ resp.success = rc.save
36
+ render :json => resp
37
+ end
38
+
39
+ end
40
+ end
41
+
@@ -126,6 +126,7 @@ module Caboose
126
126
  when 'description' then site.description = value
127
127
  when 'under_construction_html' then site.under_construction_html = value
128
128
  when 'use_store' then site.use_store = value
129
+ when 'use_retargeting' then site.use_retargeting = value
129
130
  end
130
131
  end
131
132
 
@@ -26,5 +26,20 @@ tinyMCE.init({
26
26
  def parent_categories
27
27
  Caboose::Category.find(1).children.where(:status => 'Active')
28
28
  end
29
+
30
+ def analytics_js
31
+ return "" if @site.analytics_id.nil? || @site.analytics_id.strip.length == 0
32
+ str = ''
33
+ str << "<script>\n"
34
+ str << " (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n"
35
+ str << " (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n"
36
+ str << " m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n"
37
+ str << " })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n"
38
+ str << " ga('create', '#{@site.analytics_id}', 'auto');\n"
39
+ str << " ga('send', 'pageview');\n"
40
+ str << "</script>\n"
41
+ return str
42
+ end
43
+
29
44
  end
30
45
  end
@@ -7,7 +7,7 @@ class Caboose::Page < ActiveRecord::Base
7
7
  has_many :children, :class_name => 'Caboose::Page', :foreign_key => 'parent_id', :order => 'sort_order, title'
8
8
  has_many :page_permissions
9
9
  has_many :blocks, :order => 'sort_order'
10
- has_many :page_tags, :class_name => 'Caboose::PageTag', :dependent => :delete_all, :order => 'tag'
10
+ has_many :page_tags, :class_name => 'Caboose::PageTag', :dependent => :delete_all, :order => 'tag'
11
11
  has_one :page_cache
12
12
  attr_accessible :id ,
13
13
  :site_id ,
@@ -0,0 +1,40 @@
1
+ module Caboose
2
+ class RetargetingConfig < ActiveRecord::Base
3
+ self.table_name = "retargeting_configs"
4
+
5
+ belongs_to :site
6
+ attr_accessible :id,
7
+ :site_id,
8
+ :conversion_id,
9
+ :labels_function
10
+
11
+ def labels(page)
12
+ return eval(self.custom_labels_function)
13
+ end
14
+
15
+ def js_code(request, page)
16
+ return "" if !self.site.use_retargeting
17
+
18
+ str = ""
19
+ labels = self.labels(request, page)
20
+ labels.each do |label|
21
+ str << "<script type='text/javascript'>\n"
22
+ str << "/* <![CDATA[ */\n"
23
+ str << "var google_conversion_id = #{self.conversion_id};\n"
24
+ str << "var google_conversion_label = '#{label}';\n"
25
+ str << "var google_custom_params = window.google_tag_params;\n"
26
+ str << "var google_remarketing_only = true;\n"
27
+ str << "/* ]]> */\n"
28
+ str << "</script>\n"
29
+ str << "<script type='text/javascript' src='//www.googleadservices.com/pagead/conversion.js'></script>\n"
30
+ str << "<noscript>\n"
31
+ str << "<div style='display:inline;'>\n"
32
+ str << "<img height='1' width='1' style='border-style:none;' alt='' src='//googleads.g.doubleclick.net/pagead/viewthroughconversion/#{self.conversion_id}/?value=1.00&amp;currency_code=USD&amp;label=#{label}&amp;guid=ON&amp;script=0'/>\n"
33
+ str << "</div>\n"
34
+ str << "</noscript>\n"
35
+ end
36
+ return str
37
+ end
38
+
39
+ end
40
+ end
@@ -130,7 +130,7 @@ class Caboose::Schema < Caboose::Utilities::Schema
130
130
  [ :country , :string ],
131
131
  [ :country_code , :string ],
132
132
  [ :phone , :string ]
133
- ],
133
+ ],
134
134
  Caboose::Asset => [
135
135
  [ :page_id , :integer ],
136
136
  [ :user_id , :integer ],
@@ -502,6 +502,11 @@ class Caboose::Schema < Caboose::Utilities::Schema
502
502
  [ :product_image_id , :integer ],
503
503
  [ :variant_id , :integer ]
504
504
  ],
505
+ Caboose::RetargetingConfig => [
506
+ [ :site_id , :integer ],
507
+ [ :conversion_id , :string ],
508
+ [ :labels_function , :text ]
509
+ ],
505
510
  Caboose::Review => [
506
511
  [ :product_id , :integer ],
507
512
  [ :content , :string ],
@@ -568,13 +573,15 @@ class Caboose::Schema < Caboose::Utilities::Schema
568
573
  [ :under_construction_html , :text ],
569
574
  [ :use_store , :boolean , { :default => false }],
570
575
  [ :logo , :attachment ],
571
- [ :is_master , :boolean , { :default => false }]
576
+ [ :is_master , :boolean , { :default => false }],
577
+ [ :analytics_id , :string ],
578
+ [ :use_retargeting , :boolean , { :default => false }]
572
579
  ],
573
580
  Caboose::SiteMembership => [
574
581
  [ :site_id , :integer ],
575
582
  [ :user_id , :integer ],
576
583
  [ :role , :string ]
577
- ],
584
+ ],
578
585
  Caboose::SmtpConfig => [
579
586
  [ :site_id , :integer ],
580
587
  [ :address , :string , { :default => 'localhost' }],
@@ -646,7 +653,7 @@ class Caboose::Schema < Caboose::Utilities::Schema
646
653
  [ :weight_unit , :string , { :default => 'oz' }],
647
654
  [ :download_url_expires_in , :string , { :default => 5 }],
648
655
  [ :starting_order_number , :integer , { :default => 1000 }]
649
- ],
656
+ ],
650
657
  Caboose::User => [
651
658
  [ :site_id , :integer ],
652
659
  [ :first_name , :string ],
@@ -21,10 +21,20 @@ class Caboose::Site < ActiveRecord::Base
21
21
 
22
22
  def smtp_config
23
23
  c = Caboose::SmtpConfig.where(:site_id => self.id).first
24
+ c = Caboose::SmtpConfig.create(:site_id => self.id) if c.nil?
25
+ return c
24
26
  end
25
27
 
26
28
  def social_config
27
29
  s = Caboose::SocialConfig.where(:site_id => self.id).first
30
+ s = Caboose::SocialConfig.create(:site_id => self.id) if s.nil?
31
+ return s
32
+ end
33
+
34
+ def retargeting_config
35
+ c = Caboose::RetargetingConfig.where(:site_id => self.id).first
36
+ c = Caboose::RetargetingConfig.create(:site_id => self.id) if c.nil?
37
+ return c
28
38
  end
29
39
 
30
40
  def self.id_for_domain(domain)
@@ -0,0 +1,42 @@
1
+ <%
2
+ s = @site
3
+ rc = s.retargeting_config
4
+ %>
5
+ <%= render :partial => 'caboose/sites/admin_header' %>
6
+
7
+ <p><div id='site_<%= s.id %>_use_retargeting' ></div></p>
8
+ <p><div id='retargetingconfig_<%= rc.id %>_conversion_id' ></div></p>
9
+ <h2>Conversion Labels</h2>
10
+ <p><code>def conversion_labels_function(request, page) {</code></p>
11
+ <p><div id='retargetingconfig_<%= rc.id %>_labels_function' ></div></p>
12
+ <p><code># This function returns an array of conversion labels<br />}</code></p>
13
+
14
+ <%= render :partial => 'caboose/sites/admin_footer' %>
15
+
16
+ <% content_for :caboose_js do %>
17
+ <script type="text/javascript">
18
+
19
+ $(document).ready(function() {
20
+ new ModelBinder({
21
+ name: 'Site',
22
+ id: <%= @site.id %>,
23
+ update_url: "/admin/sites/<%= @site.id %>",
24
+ authenticity_token: '<%= form_authenticity_token %>',
25
+ attributes: [
26
+ { name: 'use_retargeting' , nice_name: 'Use Retargeting' , type: 'checkbox' , value: <%= raw Caboose.json(@site.use_retargeting ? true : false) %>, width: 400 }
27
+ ]
28
+ });
29
+ new ModelBinder({
30
+ name: 'RetargetingConfig',
31
+ id: <%= rc.id %>,
32
+ update_url: "/admin/sites/<%= @site.id %>/retargeting",
33
+ authenticity_token: '<%= form_authenticity_token %>',
34
+ attributes: [
35
+ { name: 'conversion_id' , nice_name: 'Conversion ID' , type: 'text' , value: <%= raw Caboose.json(rc.conversion_id ) %>, width: 600 },
36
+ { name: 'labels_function' , nice_name: 'Conversion Labels Function' , type: 'textarea' , value: <%= raw Caboose.json(rc.labels_function) %>, width: 600, height: 75, fixed_placeholder: false }
37
+ ]
38
+ });
39
+ });
40
+
41
+ </script>
42
+ <% end %>
@@ -24,8 +24,9 @@
24
24
  <ul id='tabs'>
25
25
  <%
26
26
  tabs = {
27
- 'General' => "/admin/sites/#{@site.id}",
28
- 'Block Types' => "/admin/sites/#{@site.id}/block-types",
27
+ 'General' => "/admin/sites/#{@site.id}",
28
+ 'Block Types' => "/admin/sites/#{@site.id}/block-types",
29
+ 'Retargeting' => "/admin/sites/#{@site.id}/retargeting",
29
30
  'Delete Site' => "/admin/sites/#{@site.id}/delete"
30
31
  }
31
32
 
data/config/routes.rb CHANGED
@@ -48,13 +48,20 @@ Caboose::Engine.routes.draw do
48
48
 
49
49
  get "my-account" => "my_account#index"
50
50
  put "my-account" => "my_account#update"
51
-
51
+
52
+ #=============================================================================
53
+ # Retargeting
54
+ #=============================================================================
55
+
56
+ get "admin/sites/:site_id/retargeting" => "retargeting#admin_edit"
57
+ put "admin/sites/:site_id/retargeting" => "retargeting#admin_update"
58
+
52
59
  #=============================================================================
53
60
  # Sites
54
61
  #=============================================================================
55
62
 
56
63
  get "admin/sites/options" => "sites#options"
57
- get "admin/sites/new" => "sites#admin_new"
64
+ get "admin/sites/new" => "sites#admin_new"
58
65
  get "admin/sites/:id/block-types" => "sites#admin_edit_block_types"
59
66
  get "admin/sites/:id/delete" => "sites#admin_delete_form"
60
67
  get "admin/sites/:id" => "sites#admin_edit"
@@ -71,7 +78,7 @@ Caboose::Engine.routes.draw do
71
78
  put "admin/sites/:site_id/domains/:id" => "domains#admin_update"
72
79
  delete "admin/sites/:site_id/domains/:id" => "domains#admin_delete"
73
80
  put "admin/sites/:site_id/domains/:id/set-primary" => "domains#admin_set_primary"
74
-
81
+
75
82
  #=============================================================================
76
83
  # Store
77
84
  #=============================================================================
@@ -1,3 +1,3 @@
1
1
  module Caboose
2
- VERSION = '0.5.189'
2
+ VERSION = '0.5.190'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caboose-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.189
4
+ version: 0.5.190
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Barry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-31 00:00:00.000000000 Z
11
+ date: 2015-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -454,6 +454,8 @@ files:
454
454
  - app/assets/javascripts/caboose/model/s3.js
455
455
  - app/assets/javascripts/caboose/my_account_edit_order.js
456
456
  - app/assets/javascripts/caboose/product.js
457
+ - app/assets/javascripts/caboose/product_new.js
458
+ - app/assets/javascripts/caboose/product_old.js
457
459
  - app/assets/javascripts/caboose/s3upload.js
458
460
  - app/assets/javascripts/caboose/shortcut.js
459
461
  - app/assets/javascripts/caboose/spectrum.js
@@ -547,6 +549,7 @@ files:
547
549
  - app/controllers/caboose/products_controller.rb
548
550
  - app/controllers/caboose/redirects_controller.rb
549
551
  - app/controllers/caboose/register_controller.rb
552
+ - app/controllers/caboose/retargeting_controller.rb
550
553
  - app/controllers/caboose/reviews_controller.rb
551
554
  - app/controllers/caboose/roles_controller.rb
552
555
  - app/controllers/caboose/settings_controller.rb
@@ -634,6 +637,7 @@ files:
634
637
  - app/models/caboose/product_category_sort.rb
635
638
  - app/models/caboose/product_image.rb
636
639
  - app/models/caboose/product_image_variant.rb
640
+ - app/models/caboose/retargeting_config.rb
637
641
  - app/models/caboose/review.rb
638
642
  - app/models/caboose/rich_text_block_parser.rb
639
643
  - app/models/caboose/role.rb
@@ -854,6 +858,7 @@ files:
854
858
  - app/views/caboose/redirects/admin_index.html.erb
855
859
  - app/views/caboose/redirects/admin_new.html.erb
856
860
  - app/views/caboose/register/index.html.erb
861
+ - app/views/caboose/retargeting/admin_edit.html.erb
857
862
  - app/views/caboose/roles/edit.html.erb
858
863
  - app/views/caboose/roles/index.html.erb
859
864
  - app/views/caboose/roles/new.html.erb