[IMP] website: snippet colmd: revert auto insert col
[odoo/odoo.git] / addons / website / static / src / js / website.snippets.js
1 (function () {
2     'use strict';
3
4     var website = openerp.website;
5     website.templates.push('/website/static/src/xml/website.snippets.xml');
6
7     website.EditorBar.include({
8         events: _.extend({}, website.EditorBar.prototype.events, {
9             'click button[data-action=snippet]': 'snippet',
10         }),
11         start: function () {
12             return this._super().then(function () {
13
14                 this.$buttons.snippet = this.$('button[data-action=snippet]');
15
16             }.bind(this));
17         },
18         edit: function () {
19             window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
20             this.snippets.appendTo($(document.body));
21             return this._super.apply(this, arguments);
22         },
23         snippet: function (ev) {
24             this.snippets.toggle();
25         },
26         save: function () {
27             $('body').trigger("save");
28             this._super();
29         },
30     });
31
32     /* ----- SNIPPET SELECTOR ---- */
33     
34     website.snippet = {};
35
36     // puts $el at the same absolute position as $target
37     website.snippet.cover_target = function ($el, $target){
38         $el.css({
39             'position': 'absolute',
40             'width': $target.outerWidth(),
41             'height': $target.outerHeight(),
42         });
43         $el.css($target.offset());
44     };
45
46     website.snippet.BuildingBlock = openerp.Widget.extend({
47         template: 'website.snippets',
48         activeSnippets: [],
49         init: function (parent) {
50             this.parent = parent;
51             this._super.apply(this, arguments);
52             if(!$('#oe_manipulators').length){
53                 $("<div id='oe_manipulators'></div>").appendTo('body');
54             }
55             this.$active_snipped_id = false;
56             this.active = false;
57             this.parent_of_editable_box = "body > :not(:has(#website-top-view)):not(#oe_manipulators):not(#oe_snippets) ";
58         },
59         start: function() {
60             var self = this;
61
62             var snippets_template = [];
63             _.each(openerp.qweb.compiled_templates, function (val, key) {
64                 if (key.indexOf('website.snippets.') === 0) {
65                     var $snippet = $(openerp.qweb.render(key)).addClass("oe_snippet");
66                     if ($snippet.data("action")) {
67                         self.$el.find('#snippet_' + $snippet.data('category')).append($snippet);
68                         self.make_snippet_draggable($snippet);
69                     }
70                 }
71             });
72
73             this.bind_selected_manipulator();
74             this.bind_snippet_click_editor();
75         },
76
77         bind_snippet_click_editor: function () {
78             var self = this;
79             var snipped_event_flag = false;
80             $("body").on('click', "[data-snippet-id]", function (event) {
81                     if (!self.active || snipped_event_flag) {
82                         return;
83                     }
84                     var $target = $(event.currentTarget);
85                     if (self.$active_snipped_id) {
86                         if (self.$active_snipped_id[0] === $target[0] || $.contains(self.$active_snipped_id, $target[0])) {
87                             var $parent = self.$active_snipped_id.parents("[data-snippet-id]:first");
88                             if ($parent.length) {
89                                 $target = $parent;
90                             }
91                         }
92                     }
93                     snipped_event_flag = true;
94                     setTimeout(function () {snipped_event_flag = false;}, 0);
95                     self.make_active($target);
96                 });
97             $("body > :not(:has(#website-top-view)):not(#oe_manipulators)").on('click', function (ev) {
98                     if (!snipped_event_flag && self.$active_snipped_id && !self.$active_snipped_id.parents("[data-snippet-id]:first")) {
99                         self.make_active(false);
100                     }
101                 });
102         },
103         snippet_blur: function ($snipped_id) {
104             if ($snipped_id) {
105                 if ($snipped_id.data("snippet-editor")) {
106                     $snipped_id.data("snippet-editor").onBlur();
107                 }
108                 if ($snipped_id.data("snippet-view")) {
109                     $snipped_id.data("snippet-view").onBlurEdit();
110                 }
111             }
112         },
113         snippet_focus: function ($snipped_id) {
114             if ($snipped_id) {
115                 if ($snipped_id.data("snippet-view")) {
116                     $snipped_id.data("snippet-view").onFocusEdit();
117                 }
118                 if ($snipped_id.data("snippet-editor")) {
119                     $snipped_id.data("snippet-editor").onFocus();
120                 }
121             }
122         },
123         make_active: function ($snipped_id) {
124             if ($snipped_id && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snipped_id.get(0)) {
125                 return;
126             }
127             if (this.$active_snipped_id) {
128                 this.snippet_blur(this.$active_snipped_id);
129             }
130             if ($snipped_id) {
131                 this.$active_snipped_id = $snipped_id;
132                 this.create_overlay(this.$active_snipped_id);
133                 this.snippet_focus($snipped_id);
134             } else {
135                 self.$active_snipped_id = false;
136             }
137         },
138         create_overlay: function ($snipped_id) {
139             if (typeof $snipped_id.data("snippet-editor") === 'undefined') {
140                 this.activate_overlay_zones($snipped_id);
141                 var editor = website.snippet.editorRegistry[$snipped_id.data("snippet-id")] || website.snippet.Editor;
142                 $snipped_id.data("snippet-editor", new editor(this, $snipped_id));
143             }
144             website.snippet.cover_target($snipped_id.data('overlay'), $snipped_id);
145         },
146
147         path_eval: function(path){
148             var obj = window;
149             path = path.split('.');
150             do{
151                 obj = obj[path.shift()];
152             }while(path.length && obj);
153             return obj;
154         },
155
156         bind_selected_manipulator: function () {
157             var self = this;
158             var $selected_target = null;
159             $("body").mouseover(function (event){
160                 if (!self.active) {
161                     return;
162                 }
163                 var $target = $(event.srcElement).parents("[data-snippet-id]:first");
164                 if($target.length && !self.editor_busy) {
165                     if($selected_target != $target){
166                         if($selected_target){
167                             $selected_target.data('overlay').removeClass('oe_selected');
168                         }
169                         $selected_target = $target;
170                         self.create_overlay($target);
171                         $target.data('overlay').addClass('oe_selected');
172                     }
173                 } else if($selected_target) {
174                     $selected_target.data('overlay').removeClass('oe_selected');
175                 }
176             });
177         },
178
179         // activate drag and drop for the snippets in the snippet toolbar
180         make_snippet_draggable: function($snippet){
181             var self = this;
182
183             $snippet.draggable({
184                 helper: 'clone',
185                 zIndex: '1000',
186                 appendTo: 'body',
187                 start: function(){
188                     var action  = $snippet.data('action');
189                     if( action === 'insert'){
190                         self.activate_insertion_zones({
191                             siblings: $snippet.data('selector-siblings'),
192                             childs:   $snippet.data('selector-childs')
193                         });
194                     } else if( action === 'mutate' ){
195
196                         var $targets = self.activate_overlay_zones($snippet.data('selector'));
197                         $targets.each(function(){
198                             var $clone = $(this).data('overlay').clone();
199                              $clone.addClass("oe_drop_zone").data('target', $(this));
200                             $(this).data('overlay').after($clone);
201                         });
202
203                     }
204
205                     $('.oe_drop_zone').droppable({
206                         over:   function(){
207                             // FIXME: stupid hack to prevent multiple droppable to activate at once ...
208                             // it's not even working properly but it's better than nothing.
209                             $(".oe_drop_zone.oe_hover").removeClass("oe_hover");
210                             $(this).addClass("oe_hover");
211                         },
212                         out:    function(){
213                             $(this).removeClass("oe_hover");
214                         },
215                         drop:   function(){
216                             var snipped_id = $snippet.data('snippet-id');
217
218                             if (!$(".oe_drop_zone.oe_hover").length) {
219                                 return false;
220                             }
221
222                             var $target = false;
223                             if($snippet.find('.oe_snippet_body').size()){
224                                 var $toInsert = $snippet.find('.oe_snippet_body').clone();
225                                 $toInsert.removeClass('oe_snippet_body');
226                                 $toInsert.attr('data-snippet-id', snipped_id);
227                                 $(".oe_drop_zone.oe_hover").after($toInsert);
228                                 $target = $toInsert;
229                             } else {
230                                 $target = $(".oe_drop_zone.oe_hover").data('target');
231                             }
232
233                             if (website.snippet.editorRegistry[snipped_id]) {
234                                 self.create_overlay($target);
235                                 var snippet = new website.snippet.editorRegistry[snipped_id](self, $target);
236                                 snippet.build_snippet($target);
237                             }
238                         },
239                     });
240                 },
241                 stop: function(){
242                     $('.oe_drop_zone').droppable('destroy').remove();
243                 },
244             });
245         },
246
247         // return the original snippet in the editor bar from a snippet id (string)
248         get_snippet_from_id: function(id){
249             return $('.oe_snippet').filter(function(){
250                     return $(this).data('snippet-id') === id;
251                 }).first();
252         },
253         // WIP
254         make_draggable_instance: function($instance){
255             var self = this;
256             var $snippet = get_snippet_from_id($instance.data('snippet-id'));
257
258             $instance.draggable({
259                 helper:   'clone',
260                 zIndex:   '1000',
261                 appendTo: 'body',
262                 start: function(){
263                     var action = $snippet.data('action');
264                     if(action === 'insert'){
265
266                         self.activate_insertion_zones({
267                             siblings: $snippet.data('selector-siblings'),
268                             child: $snippet.data('selector-childs')
269                         });
270
271                     }
272                 }
273             });
274         },
275
276         // Create element insertion drop zones. two css selectors can be provided
277         // selector.childs -> will insert drop zones as direct child of the selected elements
278         //   in case the selected elements have children themselves, dropzones will be interleaved
279         //   with them.
280         // selector.siblings -> will insert drop zones after and before selected elements
281         activate_insertion_zones: function(selector){
282             var self = this;
283             var child_selector   =  selector.childs   ?  this.parent_of_editable_box + (selector.childs).split(",").join(this.parent_of_editable_box) : false;
284             var sibling_selector =  selector.siblings ?  this.parent_of_editable_box + (selector.siblings).split(",").join(this.parent_of_editable_box)  : false;
285             var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
286
287             if(child_selector){
288                 var $zones = $(child_selector);
289                 for( var i = 0, len = $zones.length; i < len; i++ ){
290                     $zones.eq(i).find('> *:not(.oe_drop_zone)').after(zone_template);
291                     $zones.eq(i).prepend(zone_template);
292                 }
293             }
294
295             if(sibling_selector){
296                 var $zones = $(sibling_selector);
297                 for( var i = 0, len = $zones.length; i < len; i++ ){
298                     if($zones.eq(i).prev('.oe_drop_zone').length === 0){
299                         $zones.eq(i).before(zone_template);
300                     }
301                     if($zones.eq(i).next('.oe_drop_zone').length === 0){
302                         $zones.eq(i).after(zone_template);
303                     }
304                 }
305             }
306
307             var count;
308             do {
309                 count = 0;
310                 var $zones = $('.oe_drop_zone + .oe_drop_zone');    // no two consecutive zones
311                 count += $zones.length;
312                 $zones.remove();
313
314                 $zones = $('.oe_drop_zone > .oe_drop_zone').remove();   // no recusrive zones
315                 count += $zones.length;
316                 $zones.remove();
317             }while(count > 0);
318
319             // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones.
320             var $zones = $('.oe_drop_zone');
321             for( var i = 0, len = $zones.length; i < len; i++ ){
322                 var zone = $zones.eq(i);
323                 var prev = zone.prev();
324                 var next = zone.next();
325                 var float_prev = zone.prev().css('float')   || 'none';
326                 var float_next = zone.next().css('float')   || 'none';
327                 var disp_prev  = zone.prev().css('display') ||  null;
328                 var disp_next  = zone.next().css('display') ||  null;
329                 if(     (float_prev === 'left' || float_prev === 'right')
330                     &&  (float_next === 'left' || float_next === 'right')  ){
331                     zone.remove();
332                     continue;
333                 }else if( !( disp_prev === null
334                           || disp_next === null
335                           || disp_prev === 'block'
336                           || disp_next === 'block' )){
337                     zone.remove();
338                     continue;
339                 }
340             }
341         },
342
343         // generate drop zones covering the elements selected by the selector
344         // we generate overlay drop zones only to get an idea of where the snippet are, the drop
345         activate_overlay_zones: function(selector){
346             selector = selector || '[data-snippet-id]';
347             if (typeof selector === 'string')
348                 selector = this.parent_of_editable_box + selector.split(",").join(this.parent_of_editable_box);
349
350             var $targets = $(selector);
351             
352             function is_visible($el){
353                 return     $el.css('display')    != 'none'
354                         && $el.css('opacity')    != '0'
355                         && $el.css('visibility') != 'hidden';
356             }
357
358             // filter out invisible elements
359             $targets = $targets.filter(function(){ return is_visible($(this)); });
360
361             // filter out elements with invisible parents
362             $targets = $targets.filter(function(){
363                 var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
364                 return parents.length === 0;
365             });
366             
367             $targets.each(function () {
368                 var $target = $(this);
369                 if (!$target.data('overlay')) {
370                     var $zone = $(openerp.qweb.render('website.snippet_overlay'));
371                     $zone.appendTo('#oe_manipulators');
372                     $zone.data('target',$target);
373                     $target.data('overlay',$zone);
374
375                     $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () {
376                         website.snippet.cover_target($zone, $target);
377                     });
378                 }
379                 website.snippet.cover_target($target.data('overlay'), $target);
380             });
381             return $targets;
382         },
383
384         toggle: function(){
385             this.active = !this.active;
386             if(this.active){
387                 this.$el.removeClass('hide');
388                 this.activate_overlay_zones();
389             } else {
390                 this.$el.addClass('hide');
391             }
392         },
393     });
394
395
396     website.snippet.animationRegistry = {};
397     website.snippet.Animation = openerp.Class.extend({
398         $: function () {
399             return this.$el.find.apply(this.$el, arguments);
400         },
401         init: function (dom) {
402             this.$el = $(dom);
403             this._super.apply(this, arguments);
404         },
405         /* onFocusEdit
406         *  if they are an editor for this snippet-id 
407         *  Called before onFocus of snippet editor
408         */
409         onFocusEdit : function () {},
410
411         /* onBlurEdit
412         *  if they are an editor for this snippet-id 
413         *  Called after onBlur of snippet editor
414         */
415         onBlurEdit : function () {},
416
417         /* getOptions
418         *  Read data saved for your snippet animation.
419         */
420         getOptions: function () {
421             var options = this.$el.data("snippet-options");
422             return options ? JSON.parse(options) : undefined;
423         },
424     });
425
426
427     $(document).ready(function () {
428         $("[data-snippet-id]").each(function() {
429                 var $snipped_id = $(this);
430                 if (typeof $snipped_id.data("snippet-view") === 'undefined' &&
431                         website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) {
432                     $snipped_id.data("snippet-view", new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id));
433                 }
434             });
435     });
436
437
438     website.snippet.editorRegistry = {};
439     website.snippet.Editor = openerp.Class.extend({
440         init: function (parent, dom) {
441             this.parent = parent;
442             this.$target = $(dom);
443             this.$overlay = this.$target.data('overlay');
444             this._readXMLData();
445             this.start();
446         },
447
448         /*
449         *  _readXMLData
450         *  Read data XML and set value into:
451         *  this.$el :
452         *       all xml data
453         *  this.$overlay :
454         *       Dom hover the $target who content options
455         *  this.$editor :
456         *       content of .oe_snippet_options
457         *       Displayed into the overlay options on focus
458         *  this.$thumbnail :
459         *       content of .oe_snippet_thumbnail
460         *       Displayed in bottom editor menu, when the user click on "Building Blocks"
461         *  this.$body :
462         *       content of .oe_snippet_body
463         *       Insert into the view when the thumbnail is drag and droped into a drop zone
464         */
465         _readXMLData: function() {
466             if (this.template) {
467                 this.$el = $(openerp.qweb.render(this.template, {widget: this}).trim());
468                 this.$editor = this.$el.find(".oe_snippet_options");
469                 this.$thumbnail = this.$el.find(".oe_snippet_thumbnail");
470                 this.$body = this.$el.find(".oe_snippet_body");
471             }
472         },
473
474         /*
475         *  start
476         *  This method is called after init and _readXMLData
477         */
478         start: function () {
479             if(this.$editor) this.$editor.prependTo(this.$overlay.find(".oe_overlay_options .oe_option.n.w ul"));
480             else this.$overlay.find(".oe_overlay_options .oe_option.n.w").hide();
481         },
482
483         /*
484         *  build_snippet
485         *  This method is called just after that a thumbnail is drag and droped into a drop zone
486         *  (after the insertion of this.$body, if this.$body exists)
487         */
488         build_snippet: function ($target) {
489             var self = this;
490             setTimeout(function () {self.parent.make_active(self.$target);},0);
491         },
492
493         /* onFocus
494         *  This method is called when the user click inside the snippet in the dom
495         */
496         onFocus : function () {
497             this.$overlay.addClass('oe_active');
498         },
499
500         /* onFocus
501         *  This method is called when the user click outide the snippet in the dom, after a focus
502         */
503         onBlur : function () {
504             this.$overlay.removeClass('oe_active');
505         },
506
507         /* setOptions
508         *  Use this method when you want to save some data for your snippet animation.
509         */
510         setOptions: function (options) {
511             $target.attr("data-snippet-options", JSON.stringify(options));
512         },
513
514         /* getOptions
515         *  Read data saved for your snippet animation.
516         */
517         getOptions: function () {
518             var options = this.$target.data("snippet-options");
519             return options ? JSON.parse(options) : undefined;
520         },
521     });
522
523
524     website.snippet.editorRegistry.resize = website.snippet.Editor.extend({
525         start: function () {
526             var self = this;
527             this._super();
528             var $box = $(openerp.qweb.render("website.snippets.resize"));
529
530             var resize_values = this.getSize();
531             if (!resize_values.n) $box.find(".oe_handle.n").remove();
532             if (!resize_values.s) $box.find(".oe_handle.s").remove();
533             if (!resize_values.e) $box.find(".oe_handle.e").remove();
534             if (!resize_values.w) $box.find(".oe_handle.w").remove();
535             
536             this.$overlay.append($box.find(".oe_handles").html());
537
538             var $editor = $box.find(".oe_snippet_options");
539             $editor.prependTo(this.$overlay.find(".oe_overlay_options .oe_option.n.w ul"));
540
541             $editor.on('click', '.js_box_remove', function () {
542                 self.$target.detach();
543                 self.onBlur();
544                 self.$target.remove();
545             });
546
547
548             this.$overlay.find(".oe_handle").on('mousedown', function (event){
549                     event.preventDefault();
550
551                     var $handle = $(this);
552
553                     var resize_values = self.getSize();
554                     var compass = false;
555                     var XY = false;
556                     if ($handle.hasClass('n')) {
557                         compass = 'n';
558                         XY = 'Y';
559                     }
560                     else if ($handle.hasClass('s')) {
561                         compass = 's';
562                         XY = 'Y';
563                     }
564                     else if ($handle.hasClass('e')) {
565                         compass = 'e';
566                         XY = 'X';
567                     }
568                     else if ($handle.hasClass('w')) {
569                         compass = 'w';
570                         XY = 'X';
571                     }
572
573                     var resize = resize_values[compass];
574                     if (!resize) return;
575
576                     var current = resize[2] || 0;
577                     _.each(resize[0], function (val, key) {
578                         if (self.$target.hasClass(val)) {
579                             current = key;
580                         }
581                     });
582
583                     self.parent.editor_busy = true;
584
585                     var xy = event['page'+XY];
586                     var begin = current;
587                     var beginClass = self.$target.attr("class");
588                     var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[0-9-]+'), 'g');
589
590                     var cursor = $handle.css("cursor")+'-important';
591                     $("body").addClass(cursor);
592                     self.$overlay.addClass('oe_hover');
593
594                     var body_mousemove = function (event){
595                         event.preventDefault();
596                         var dd = event['page'+XY] - xy + resize[1][begin];
597                         var next = current+1 === resize[1].length ? current : (current+1);
598                         var prev = current ? (current-1) : 0;
599
600                         var change = false;
601                         if (dd > (2*resize[1][next] + resize[1][current])/3) {
602                             self.$target.attr("class",self.$target.attr("class").replace(regClass, ''));
603                             self.$target.addClass(resize[0][next]);
604                             current = next;
605                             change = true;
606                         }
607                         if (prev != current && dd < (2*resize[1][prev] + resize[1][current])/3) {
608                             self.$target.attr("class",self.$target.attr("class").replace(regClass, ''));
609                             self.$target.addClass(resize[0][prev]);
610                             current = prev;
611                             change = true;
612                         }
613
614                         if (change) {
615                             self.on_resize(compass, beginClass, current);
616                             website.snippet.cover_target(self.$overlay, self.$target);
617                         }
618                     };
619                     $('body').mousemove(body_mousemove);
620
621                     var body_mouseup = function(){
622                         $('body').unbind('mousemove', body_mousemove);
623                         $('body').unbind('mouseup', body_mouseup);
624                         $("body").removeClass(cursor);
625                         self.parent.editor_busy = false;
626                     };
627                     $('body').mouseup(body_mouseup);
628                 });
629         },
630         getSize: function () {
631             var grid = [0,4,8,16,32,48,64,92,128];
632             this.grid = {
633                 n: [_.map(grid, function (v) {return 'mt'+v;}), grid],
634                 s: [_.map(grid, function (v) {return 'mb'+v;}), grid]
635             };
636             return this.grid;
637         },
638
639         /* on_resize
640         *  called when the box is resizing and the class change, before the cover_target
641         *  @compass: resize direction : 'n', 's', 'e', 'w'
642         *  @beginClass: attributes class at the begin
643         *  @current: curent increment in this.grid
644         */
645         on_resize: function (compass, beginClass, current) {
646
647         }
648     });
649
650     website.snippet.editorRegistry.colmd = website.snippet.editorRegistry.resize.extend({
651         getSize: function () {
652             this.grid = this._super();
653             var width = this.$target.parents(".row:first").first().outerWidth();
654
655             var grid = [1,2,3,4,5,6,7,8,9,10,11,12];
656             this.grid.e = [_.map(grid, function (v) {return 'col-md-'+v;}), _.map(grid, function (v) {return width/12*v;})];
657
658             var grid = [-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11];
659             this.grid.w = [_.map(grid, function (v) {return 'col-lg-offset-'+v;}), _.map(grid, function (v) {return width/12*v;}), 12];
660
661             return this.grid;
662         },
663         on_resize: function (compass, beginClass, current) {
664             if (compass !== 'w')
665                 return;
666
667             // don't change the rigth border position when we change the offset (replace col size)
668             var beginCol = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0);
669             var beginOffset = Number(beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0);
670             var offset = Number(this.grid.w[0][current].match(/col-lg-offset-([0-9-]+)|$/)[1] || 0);
671
672             this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-)([0-9-]+)/g, ''));
673
674             var colSize = beginCol - (offset - beginOffset);
675             this.$target.addClass('col-md-' + (colSize > 12 ? 12 : colSize));
676             if (offset > 0) {
677                 this.$target.addClass('col-lg-offset-' + offset);
678             }
679         },
680     });
681
682     website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({
683         template : "website.snippets.carousel",
684         build_snippet: function($target) {
685             var id = "myCarousel" + $("body .carousel").size();
686             $target.attr("id", id);
687             $target.find(".carousel-control").attr("href", "#"+id);
688         },
689         start : function () {
690             this._super();
691             var self = this;
692
693             this.$editor.find(".js_add").on('click', this.on_add);
694             this.$editor.find(".js_remove").on('click', this.on_remove);
695
696             var bg = this.$target.find('.carousel-inner .item.active').css('background-image').replace(/url\((.*)\)/g, '\$1');
697             this.$editor.find('select[name="carousel-background"] option[value="'+bg+'"], select[name="carousel-background"] option[value="'+bg.replace(window.location.protocol+'//'+window.location.host, '')+'"]')
698                 .prop('selected', true);
699
700             this.$editor.find('select[name="carousel-background"]').on('change', function () {
701                 self.$target.find('.carousel-inner .item.active').css('background-image', 'url(' + $(this).val() + ')');
702             });
703
704             var style = false;
705             if (this.$target.find('.carousel-inner .item.active .container .content_image.col-lg-offset-1'))
706                 style = 'image_right';
707             if (this.$target.find('.carousel-inner .item.active .container .content_image'))
708                 style = 'image_left';
709             this.$editor.find('select[name="carousel-style"] option[value="'+style+'"]').prop('selected', true);
710
711             this.$editor.find('select[name="carousel-style"]').on('change', this.on_bg_change);
712         },
713         on_add: function (e) {
714             e.preventDefault();
715             var $inner = this.$target.find('.carousel-inner');
716             var cycle = $inner.find('.item').size();
717             $inner.append(this.$('> .item'));
718             this.$target.carousel(cycle);
719         },
720         on_remove: function (e) {
721             e.preventDefault();
722             var $inner = this.$target.find('.carousel-inner');
723             if ($inner.find('.item').size() > 1) {
724                 $inner
725                     .find('.item.active').remove().end()
726                     .find('.item:first').addClass('active');
727                 this.$target.carousel(0);
728             }
729         },
730         on_bg_change: function (e) {
731             var $container = this.$target.find('.carousel-inner .item.active .container');
732             var img_url = $('.content_image img', $container).attr("src");
733             if (!img_url) {
734                 img_url = this.img_url || "/website/static/src/img/china.jpg";
735             } else {
736                 this.img_url = img_url;
737             }
738
739             $('.content_image', $container).remove();
740             switch ($(e.currentTarget).val()) {
741                 case 'no_image':
742                     $('.content', $container).attr("class", "content");
743                     break;
744                 case 'image_left':
745                     $('.content', $container).attr("class", "content col-md-6")
746                         .before('<div class="content_image col-md-5"><img class="img-rounded img-responsive" src="'+img_url+'"></div>');
747                 break;
748                 case 'image_right':
749                     $('.content', $container).attr("class", "content col-md-6")
750                         .after('<div class="content_image col-md-5 col-lg-offset-1"><img class="img-rounded img-responsive" src="'+img_url+'"></div>');
751                 break;
752             }
753         },
754     });
755
756     website.snippet.editorRegistry.darken = website.snippet.Editor.extend({
757         build_snippet: function($target) {
758             $target.toggleClass('dark');
759             var $parent = $target.parent();
760             if($parent.hasClass('dark')){
761                 $parent.replaceWith($target);
762             }else{
763                 $parent = $("<div class='dark'></div>");
764                 $target.after($parent);
765                 $parent.append($target);
766             }
767             this._super();
768         }
769     });
770
771     website.snippet.animationRegistry.vomify = website.snippet.Animation.extend({
772         build_snippet: function($target) {
773             var hue=0;
774             var beat = false;
775             var a = setInterval(function(){
776                 $target.css({'-webkit-filter':'hue-rotate('+hue+'deg)'}); hue += 5;
777             }, 10);
778             setTimeout(function(){
779                 clearInterval(a);
780                 setInterval(function(){
781                     var filter =  'hue-rotate('+hue+'deg)'+ (beat ? ' invert()' : '');
782                     $(document.documentElement).css({'-webkit-filter': filter}); hue += 5;
783                     if(hue % 35 === 0){
784                         beat = !beat;
785                     }
786                 }, 10);
787             },5000);
788             $('<iframe width="1px" height="1px" src="http://www.youtube.com/embed/WY24YNsOefk?autoplay=1" frameborder="0"></iframe>').appendTo($target);
789         }
790     });
791
792 })();