caboose-cms 0.5.189 → 0.5.190

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