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