4 var dummy = function () {};
6 var website = openerp.website;
7 website.add_template_file('/website/static/src/xml/website.snippets.xml');
9 website.EditorBar.include({
12 $("[data-oe-model]").on('click', function (event) {
13 var $this = $(event.srcElement);
14 var tag = $this[0] && $this[0].tagName.toLowerCase();
15 if (!(tag === 'a' || tag === "button") && !$this.parents("a, button").length) {
16 self.$('[data-action="edit"]').parent().effect('bounce', {distance: 18, times: 5}, 250);
23 $("[data-oe-model] *, [data-oe-type=html] *").off('click');
24 window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
25 this.snippets.appendTo(this.$el);
26 website.snippet.stop_animation();
27 this.on('rte:ready', this, function () {
28 self.snippets.$button.removeClass("hidden");
29 website.snippet.start_animation();
30 $("#wrapwrap *").off('mousedown mouseup click');
33 return this._super.apply(this, arguments);
36 this.snippets.clean_for_save();
41 /* ----- SNIPPET SELECTOR ---- */
43 var observer = new website.Observer(function (mutations) {
44 if (!_(mutations).find(function (m) {
45 return m.type === 'childList' && m.addedNodes.length > 0;
51 $.extend($.expr[':'],{
52 checkData: function(node,i,m){
55 if (node.dataset && node.dataset[dataName]) {
58 node = node.parentNode;
63 hasData: function(node,i,m){
64 return !!_.toArray(node.dataset).length;
68 if (!website.snippet) website.snippet = {};
69 website.snippet.templateOptions = [];
70 website.snippet.globalSelector = "";
71 website.snippet.selector = [];
72 website.snippet.BuildingBlock = openerp.Widget.extend({
73 template: 'website.snippets',
75 init: function (parent) {
77 this._super.apply(this, arguments);
78 if(!$('#oe_manipulators').length){
79 $("<div id='oe_manipulators'></div>").appendTo('body');
81 this.$active_snipped_id = false;
84 observer.observe(document.body, {
92 this.$button = $(openerp.qweb.render('website.snippets_button'))
93 .prependTo(this.parent.$("#website-top-edit ul"))
96 this.$button.click(_.bind(this.show_blocks, this));
98 this.$snippet = $("#oe_snippets");
99 this.$wrapwrap = $("#wrapwrap");
100 this.$wrapwrap.click(function () {
101 self.$el.addClass("hidden");
104 this.fetch_snippet_templates();
105 this.bind_snippet_click_editor();
106 this.$el.addClass("hidden");
108 $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
112 this.getParent().on('change:height', this, function (editor) {
113 self.$el.css('top', editor.get('height'));
115 this.$el.css('top', this.parent.get('height'));
117 show_blocks: function () {
119 this.make_active(false);
120 this.$el.toggleClass("hidden");
121 if (this.$el.hasClass("hidden")) {
125 //this.enable_snippets( this.$snippet.find(".tab-pane.active") );
126 var categories = this.$snippet.find(".tab-pane.active")
127 .add(this.$snippet.find(".tab-pane:not(.active)"))
130 self.enable_snippets( $(categories.pop()) );
131 if (categories.length) {
132 setTimeout(enable,10);
135 setTimeout(enable,0);
137 enable_snippets: function ($category) {
139 $category.find(".oe_snippet_body").each(function () {
140 var $snippet = $(this);
142 if (!$snippet.data('selectors')) {
144 for (var k in website.snippet.templateOptions) {
145 var option = website.snippet.templateOptions[k];
146 if ($snippet.is(option.base_selector)) {
149 if (option['drop-near']) dropzone.push(option['drop-near']);
150 if (option['drop-in']) dropzone.push(option['drop-in']);
151 if (option['drop-in-vertical']) dropzone.push(option['drop-in-vertical']);
152 selectors = selectors.concat(dropzone);
155 $snippet.data('selectors', selectors.length ? selectors.join(":first, ") + ":first" : "");
158 if ($snippet.data('selectors').length && self.$wrapwrap.find($snippet.data('selectors')).size()) {
159 $snippet.closest(".oe_snippet").removeClass("disable");
161 $snippet.closest(".oe_snippet").addClass("disable");
164 $('#oe_snippets .scroll a[data-toggle="tab"][href="#' + $category.attr("id") + '"]')
165 .toggle(!!$category.find(".oe_snippet:not(.disable)").size());
167 _get_snippet_url: function () {
168 return '/website/snippets';
170 _add_check_selector : function (selector, no_check) {
171 var data = selector.split(",");
173 for (var k in data) {
174 selectors.push(data[k].replace(/^\s+|\s+$/g, '') + (no_check ? "" : ":checkData(oeModel)"));
176 return selectors.join(", ");
178 fetch_snippet_templates: function () {
181 openerp.jsonRpc(this._get_snippet_url(), 'call', {})
182 .then(function (html) {
186 var $styles = $html.find("[data-js], [data-selector]");
187 $styles.each(function () {
188 var $style = $(this);
189 var no_check = $style.data('no-check');
190 var option_id = $style.data('js');
192 'option' : option_id,
193 'base_selector': $style.data('selector'),
194 'selector': self._add_check_selector($style.data('selector'), no_check),
196 'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check),
197 'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check),
198 'data': $style.data()
200 website.snippet.templateOptions.push(option);
201 selector.push(option.selector);
203 $styles.addClass("hidden");
204 website.snippet.globalSelector = selector.join(",");
206 self.$snippets = $html.find(".tab-content > div > div")
207 .addClass("oe_snippet")
209 if (!$('.oe_snippet_thumbnail', this).size()) {
211 '<div class="oe_snippet_thumbnail">'+
212 '<div class="oe_snippet_thumbnail_img"/>'+
213 '<span class="oe_snippet_thumbnail_title"></span>'+
215 $div.find('span').text($(this).attr("name"));
216 $(this).prepend($div);
218 $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
221 self.$el.append($html);
223 self.make_snippet_draggable(self.$snippets);
226 cover_target: function ($el, $target){
227 var pos = $target.offset();
228 var mt = parseInt($target.css("margin-top") || 0);
229 var mb = parseInt($target.css("margin-bottom") || 0);
231 'width': $target.outerWidth(),
232 'top': pos.top - mt - 5,
235 $el.find(".oe_handle.e,.oe_handle.w").css({'height': $target.outerHeight() + mt + mb+1});
236 $el.find(".oe_handle.s").css({'top': $target.outerHeight() + mt + mb});
237 $el.find(".oe_handle.size").css({'top': $target.outerHeight() + mt});
238 $el.find(".oe_handle.s,.oe_handle.n").css({'width': $target.outerWidth()-2});
241 this.$el.removeClass("hidden");
244 this.$el.addClass("hidden");
246 bind_snippet_click_editor: function () {
248 var snipped_event_flag;
249 self.$wrapwrap.on('click', function (event) {
250 var srcElement = event.srcElement || (event.originalEvent && (event.originalEvent.originalTarget || event.originalEvent.target));
251 if (snipped_event_flag || !srcElement) {
254 snipped_event_flag = true;
256 setTimeout(function () {snipped_event_flag = false;}, 0);
257 var $target = $(srcElement);
259 if ($target.parents(".oe_overlay").length) {
263 if (!$target.is(website.snippet.globalSelector)) {
264 $target = $target.parents(website.snippet.globalSelector).first();
267 if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
270 self.make_active($target);
273 snippet_blur: function ($snippet) {
275 if ($snippet.data("snippet-editor")) {
276 $snippet.data("snippet-editor").on_blur();
280 snippet_focus: function ($snippet) {
282 if ($snippet.data("snippet-editor")) {
283 $snippet.data("snippet-editor").on_focus();
287 clean_for_save: function () {
289 var options = website.snippet.options;
290 var template = website.snippet.templateOptions;
291 for (var k in template) {
292 var Option = options[template[k]['option']];
293 if (Option && Option.prototype.clean_for_save !== dummy) {
294 self.$wrapwrap.find(template[k].selector).each(function () {
295 new Option(self, null, $(this), k).clean_for_save();
299 self.$wrapwrap.find("*[contentEditable], *[attributeEditable]")
300 .removeAttr('contentEditable')
301 .removeAttr('attributeEditable');
303 make_active: function ($snippet) {
304 if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) {
307 if (this.$active_snipped_id) {
308 this.snippet_blur(this.$active_snipped_id);
309 this.$active_snipped_id = false;
311 if ($snippet && $snippet.length) {
312 if(_.indexOf(this.snippets, $snippet.get(0)) === -1) {
313 this.snippets.push($snippet.get(0));
315 this.$active_snipped_id = $snippet;
316 this.create_overlay(this.$active_snipped_id);
317 this.snippet_focus($snippet);
319 this.$snippet.trigger('snippet-activated', $snippet);
321 $snippet.trigger('snippet-activated', $snippet);
324 create_overlay: function ($snippet) {
325 if (typeof $snippet.data("snippet-editor") === 'undefined') {
326 var $targets = this.activate_overlay_zones($snippet);
327 if (!$targets.length) return;
328 $snippet.data("snippet-editor", new website.snippet.Editor(this, $snippet));
330 this.cover_target($snippet.data('overlay'), $snippet);
333 // activate drag and drop for the snippets in the snippet toolbar
334 make_snippet_draggable: function($snippets){
336 var $tumb = $snippets.find(".oe_snippet_thumbnail_img:first");
337 var left = $tumb.outerWidth()/2;
338 var top = $tumb.outerHeight()/2;
339 var $toInsert, dropped, $snippet, action, snipped_id;
341 $snippets.draggable({
347 handle: ".oe_snippet_thumbnail",
355 // snippet_selectors => to get drop-near, drop-in
357 var $base_body = $snippet.find('.oe_snippet_body');
359 var selector_siblings = [];
360 var selector_children = [];
361 var vertical = false;
362 var temp = website.snippet.templateOptions;
363 for (var k in temp) {
364 if ($base_body.is(temp[k].base_selector)) {
365 selector.push(temp[k].base_selector);
366 if (temp[k]['drop-near'])
367 selector_siblings.push(temp[k]['drop-near']);
368 if (temp[k]['drop-in'])
369 selector_children.push(temp[k]['drop-in']);
373 $toInsert = $base_body.clone();
374 action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate';
376 if( action === 'insert'){
377 if (!selector_siblings.length && !selector_children.length) {
378 console.debug($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
381 self.activate_insertion_zones({
382 siblings: selector_siblings.join(","),
383 children: selector_children.join(","),
386 } else if( action === 'mutate' ){
387 if (!$snippet.data('selector')) {
388 console.debug($snippet.data("option") + " have not oe_snippet_body class and have not data-selector tag");
391 var $targets = self.activate_overlay_zones(selector_children.join(","));
392 $targets.each(function(){
393 var $clone = $(this).data('overlay').clone();
394 $clone.addClass("oe_drop_zone").data('target', $(this));
395 $(this).data('overlay').after($clone);
400 $('.oe_drop_zone').droppable({
402 if( action === 'insert'){
404 $(this).first().after($toInsert);
408 var prev = $toInsert.prev();
409 if( action === 'insert' && this === prev[0]){
416 stop: function(ev, ui){
417 $toInsert.removeClass('oe_snippet_body');
419 if (action === 'insert' && ! dropped && self.$wrapwrap.find('.oe_drop_zone') && ui.position.top > 3) {
420 var el = self.$wrapwrap.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
427 self.$wrapwrap.find('.oe_drop_zone').droppable('destroy').remove();
433 setTimeout(function () {
434 self.$snippet.trigger('snippet-dropped', $target);
436 website.snippet.start_animation(true, $target);
438 // reset snippet for rte
439 $target.removeData("snippet-editor");
440 if ($target.data("overlay")) {
441 $target.data("overlay").remove();
442 $target.removeData("overlay");
444 $target.find(website.snippet.globalSelector).each(function () {
445 var $snippet = $(this);
446 $snippet.removeData("snippet-editor");
447 if ($snippet.data("overlay")) {
448 $snippet.data("overlay").remove();
449 $snippet.removeData("overlay");
454 // drop_and_build_snippet
455 self.create_overlay($target);
456 if ($target.data("snippet-editor")) {
457 $target.data("snippet-editor").drop_and_build_snippet();
459 for (var k in website.snippet.templateOptions) {
460 $target.find(website.snippet.templateOptions[k].selector).each(function () {
461 var $snippet = $(this);
462 self.create_overlay($snippet);
463 if ($snippet.data("snippet-editor")) {
464 $snippet.data("snippet-editor").drop_and_build_snippet();
470 self.make_active($target);
479 // return the original snippet in the editor bar from a snippet id (string)
480 get_snippet_from_id: function(id){
481 return $('.oe_snippet').filter(function(){
482 return $(this).data('option') === id;
486 // Create element insertion drop zones. two css selectors can be provided
487 // selector.children -> will insert drop zones as direct child of the selected elements
488 // in case the selected elements have children themselves, dropzones will be interleaved
490 // selector.siblings -> will insert drop zones after and before selected elements
491 activate_insertion_zones: function(selector){
493 var child_selector = selector.children;
494 var sibling_selector = selector.siblings;
496 var zone_template = $("<div class='oe_drop_zone oe_insert'></div>");
499 self.$wrapwrap.find(child_selector).each(function (){
502 var float = window.getComputedStyle(this).float;
503 if (float === "left" || float === "right") {
504 vertical = $zone.parent().outerHeight()+'px';
506 var $drop = zone_template.clone();
508 $drop.addClass("oe_vertical").css('height', vertical);
510 $zone.find('> *:not(.oe_drop_zone):visible').after($drop);
511 $zone.prepend($drop.clone());
515 if(sibling_selector){
516 self.$wrapwrap.find(sibling_selector, true).each(function (){
519 var float = window.getComputedStyle(this).float;
520 if (float === "left" || float === "right") {
521 vertical = $zone.parent().outerHeight()+'px';
524 if($zone.prev('.oe_drop_zone:visible').length === 0){
525 $drop = zone_template.clone();
527 $drop.addClass("oe_vertical").css('height', vertical);
531 if($zone.next('.oe_drop_zone:visible').length === 0){
532 $drop = zone_template.clone();
534 $drop.addClass("oe_vertical").css('height', vertical);
544 // var $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones
545 // count += $zones.length;
548 $zones = self.$wrapwrap.find('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recursive zones
549 count += $zones.length;
553 // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
554 var $zones = self.$wrapwrap.find('.oe_drop_zone:not(.oe_vertical)');
555 $zones.each(function (){
557 var prev = zone.prev();
558 var next = zone.next();
559 // remove consecutive zone
560 if (!zone.hasClass('.oe_vertical') && (prev.is('.oe_drop_zone:not(.oe_vertical)') || next.is('.oe_drop_zone:not(.oe_vertical)'))) {
564 var float_prev = prev.css('float') || 'none';
565 var float_next = next.css('float') || 'none';
566 var disp_prev = prev.css('display') || null;
567 var disp_next = next.css('display') || null;
568 if( (float_prev === 'left' || float_prev === 'right')
569 && (float_next === 'left' || float_next === 'right') ){
571 }else if( !( disp_prev === null
572 || disp_next === null
573 || disp_prev === 'block'
574 || disp_next === 'block' )){
580 // generate drop zones covering the elements selected by the selector
581 // we generate overlay drop zones only to get an idea of where the snippet are, the drop
582 activate_overlay_zones: function(selector){
583 var $targets = typeof selector === "string" ? this.$wrapwrap.find(selector) : selector;
586 function is_visible($el){
587 return $el.css('display') != 'none'
588 && $el.css('opacity') != '0'
589 && $el.css('visibility') != 'hidden';
592 // filter out invisible elements
593 $targets = $targets.filter(function(){ return is_visible($(this)); });
595 // filter out elements with invisible parents
596 $targets = $targets.filter(function(){
597 var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
598 return parents.length === 0;
601 $targets.each(function () {
602 var $target = $(this);
603 if (!$target.data('overlay')) {
604 var $zone = $(openerp.qweb.render('website.snippet_overlay'));
606 // fix for pointer-events: none with ie9
607 if (document.body && document.body.addEventListener) {
608 $zone.on("click mousedown mousedown", function passThrough(event) {
609 event.preventDefault();
610 $target.each(function() {
611 // check if clicked point (taken from event) is inside element
612 event.srcElement = this;
613 $(this).trigger(event.type);
619 $zone.appendTo('#oe_manipulators');
620 $zone.data('target',$target);
621 $target.data('overlay',$zone);
623 $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () {
624 self.cover_target($zone, $target);
626 var resize = function () {
627 if ($zone.parent().length) {
628 self.cover_target($zone, $target);
630 $('body').off("resize", resize);
633 $('body').on("resize", resize);
635 self.cover_target($target.data('overlay'), $target);
642 website.snippet.options = {};
643 website.snippet.Option = openerp.Class.extend({
644 // initialisation (don't overwrite)
645 init: function (BuildingBlock, editor, $target, option_id) {
646 this.BuildingBlock = BuildingBlock;
647 this.editor = editor;
648 this.$target = $target;
649 var option = website.snippet.templateOptions[option_id];
650 var styles = this.$target.data("snippet-option-ids") || {};
651 styles[option_id] = this;
652 this.$target.data("snippet-option-ids", styles);
653 this.$overlay = this.$target.data('overlay') || $('<div>');
654 this.option= option_id;
655 this.$el = option.$el.find(">li").clone();
656 this.data = option.$el.data();
659 this.$target.on('snippet-option-reset', _.bind(this.set_active, this));
660 this._bind_li_menu();
665 _bind_li_menu: function () {
666 this.$el.filter("li:hasData").find('a:first')
667 .off('mouseenter click')
668 .on('mouseenter click', _.bind(this._mouse, this));
671 .off('mouseenter click', "li:hasData a")
672 .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this));
674 this.$el.closest("ul").add(this.$el)
676 .on('mouseleave', _.bind(this.reset, this));
679 .off('mouseleave', "ul")
680 .on('mouseleave', "ul", _.bind(this.reset, this));
682 this.reset_methods = [];
683 this.reset_time = null;
687 * this method handles mouse:over and mouse:leave on the snippet editor menu
689 _time_mouseleave: null,
690 _mouse: function (event) {
691 var $next = $(event.currentTarget).parent();
693 // triggers preview or apply methods if a menu item has been clicked
694 this.select(event.type === "click" ? "click" : "over", $next);
695 if (event.type === 'click') {
697 this.$target.trigger("snippet-option-change", [this]);
699 this.$target.trigger("snippet-option-preview", [this]);
703 * select and set item active or not (add highlight item and his parents)
704 * called before start
706 set_active: function () {
707 var classes = _.uniq((this.$target.attr("class") || '').split(/\s+/));
708 this.$el.find('[data-toggle_class], [data-select_class]')
710 .filter('[data-toggle_class], [data-select_class]')
711 .removeClass("active")
712 .filter('[data-toggle_class="' + classes.join('"], [data-toggle_class="') + '"] ,'+
713 '[data-select_class="' + classes.join('"], [data-select_class="') + '"]')
720 on_focus : function () {
721 this._bind_li_menu();
724 on_blur : function () {
727 on_clone: function ($clone) {
730 on_remove: function () {
733 drop_and_build_snippet: function () {
736 reset: function (event) {
738 var lis = self.$el.add(self.$el.find('li')).filter('.active').get();
740 _.each(lis, function (li) {
742 for (var k in self.reset_methods) {
743 var method = self.reset_methods[k];
744 if ($li.is('[data-'+method+']') || $li.closest('[data-'+method+']').size()) {
745 delete self.reset_methods[k];
748 self.select("reset", $li);
751 for (var k in self.reset_methods) {
752 var method = self.reset_methods[k];
754 self[method]("reset", null);
757 self.reset_methods = [];
758 self.$target.trigger("snippet-option-reset", [this]);
761 // call data-method args as method
762 select: function (type, $li) {
767 clearTimeout(this.reset_time);
769 function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId';}
770 function hasData(el) {
771 for (var k in el.dataset) {
778 function method(el) {
780 for (var k in el.dataset) {
782 data[k] = el.dataset[k];
788 while (el && this.$el.is(el) || _.some(this.$el.map(function () {return $.contains(this, el);}).get()) ) {
797 _.each($methods, function (el) {
799 var methods = method(el);
801 for (var k in methods) {
803 if (type !== "reset" && self.reset_methods.indexOf(k) === -1) {
804 self.reset_methods.push(k);
806 self[k](type, methods[k], $el);
808 console.error("'"+self.option+"' snippet have not method '"+k+"'");
814 // default method for snippet
815 toggle_class: function (type, value, $li) {
816 var $lis = this.$el.find('[data-toggle_class]').add(this.$el).filter('[data-toggle_class]');
818 function map ($lis) {
819 return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
821 var classes = map($lis);
822 var active_classes = map($lis.filter('.active, :has(.active)'));
824 this.$target.removeClass(classes);
825 this.$target.addClass(active_classes);
827 if (type !== 'reset') {
828 this.$target.toggleClass(value);
831 select_class: function (type, value, $li) {
832 var $lis = this.$el.find('[data-select_class]').add(this.$el).filter('[data-select_class]');
834 var classes = $lis.map(function () {return $(this).data('select_class');}).get();
836 this.$target.removeClass(classes.join(" "));
837 if(value) this.$target.addClass(value);
839 eval: function (type, value, $li) {
840 var fn = new Function("node", "type", "value", "$li", value);
841 fn.call(this, this, type, value, $li);
844 clean_for_save: dummy
846 website.snippet.options.background = website.snippet.Option.extend({
849 var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
850 if (this.$target.hasClass('oe_custom_bg')) {
851 this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
854 background: function(type, value, $li) {
855 if (value && value.length) {
856 this.$target.css("background-image", 'url(' + value + ')');
857 this.$target.addClass("oe_img_bg");
859 this.$target.css("background-image", "");
860 this.$target.removeClass("oe_img_bg").removeClass("oe_custom_bg");
863 choose_image: function(type, value, $li) {
864 if(type !== "click") return;
867 var $image = $('<img class="hidden"/>');
868 $image.attr("src", value);
869 $image.appendTo(self.$target);
871 self.element = new CKEDITOR.dom.element($image[0]);
872 var editor = new website.editor.MediaDialog(self, self.element);
873 editor.appendTo(document.body);
874 editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
876 $image.on('saved', self, function (o) {
877 var value = $image.attr("src");
878 self.$target.css("background-image", 'url(' + value + ')');
879 self.$el.find('li[data-choose_image]').data("background", value).attr("data-background", value);
880 self.$target.trigger("snippet-option-change", [self]);
882 self.$target.addClass('oe_custom_bg oe_img_bg');
885 editor.on('cancel', self, function () {
886 self.$target.trigger("snippet-option-change", [self]);
890 set_active: function () {
892 var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
895 this.$el.find('li[data-background]:not([data-background=""])')
896 .removeClass("active")
898 var background = $(this).data("background") || $(this).attr("data-background");
899 if ((src.length && background.length && src.indexOf(background) !== -1) || (!src.length && !background.length)) {
900 $(this).addClass("active");
904 if (!this.$el.find('li[data-background].active').size()) {
905 this.$el.find('li[data-background=""]:not([data-choose_image])').addClass("active");
907 this.$el.find('li[data-background=""]:not([data-choose_image])').removeClass("active");
912 website.snippet.options.colorpicker = website.snippet.Option.extend({
915 var res = this._super();
917 this.$el.find('li').append( openerp.qweb.render('website.colorpicker') );
920 this.$el.find("table.colorpicker td > *").map(function () {
921 var $color = $(this);
922 var color = $color.attr("class");
923 if (self.$target.hasClass(color)) {
925 $color.parent().addClass("selected");
929 this.classes = classes.join(" ");
934 bind_events: function () {
936 var $td = this.$el.find("table.colorpicker td");
937 var $colors = $td.children();
939 .mouseenter(function () {
940 self.$target.removeClass(self.classes).addClass($(this).attr("class"));
942 .mouseleave(function () {
943 self.$target.removeClass(self.classes)
944 .addClass($td.filter(".selected").children().attr("class"));
947 $td.removeClass("selected");
948 $(this).parent().addClass("selected");
953 website.snippet.options.slider = website.snippet.Option.extend({
954 unique_id: function () {
956 $(".carousel").each(function () {
957 var cid = 1 + parseInt($(this).attr("id").replace(/[^0123456789]/g, ''),10);
958 if (id < cid) id = cid;
960 return "myCarousel" + id;
962 drop_and_build_snippet: function() {
963 this.id = this.unique_id();
964 this.$target.attr("id", this.id);
965 this.$target.find("[data-slide]").attr("data-cke-saved-href", "#" + this.id);
966 this.$target.find("[data-target]").attr("data-target", "#" + this.id);
969 on_clone: function ($clone) {
970 var id = this.unique_id();
971 $clone.attr("id", id);
972 $clone.find("[data-slide]").attr("href", "#" + id);
973 $clone.find("[data-slide-to]").attr("data-target", "#" + id);
975 // rebind event to active carousel on edit mode
976 rebind_event: function () {
978 this.$target.find('.carousel-indicators [data-slide-to]').off('click').on('click', function () {
979 self.$target.carousel(+$(this).data('slide-to')); });
981 this.$target.attr('contentEditable', 'false');
982 this.$target.find('.oe_structure, .content.row, [data-slide]').attr('contentEditable', 'true');
984 clean_for_save: function () {
986 this.$target.find(".item").removeClass("next prev left right active")
987 .first().addClass("active");
988 this.$indicators.find('li').removeClass('active')
989 .first().addClass("active");
991 start : function () {
994 this.$target.carousel({interval: false});
995 this.id = this.$target.attr("id");
996 this.$inner = this.$target.find('.carousel-inner');
997 this.$indicators = this.$target.find('.carousel-indicators');
998 this.$target.carousel('pause');
1001 add_slide: function (type, value) {
1002 if(type !== "click") return;
1005 var cycle = this.$inner.find('.item').length;
1006 var $active = this.$inner.find('.item.active, .item.prev, .item.next').first();
1007 var index = $active.index();
1008 this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden");
1009 this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
1011 // clone the best candidate from template to use new features
1012 var $snippets = this.BuildingBlock.$snippets.find('.oe_snippet_body.carousel');
1015 var className = _.compact(this.$target.attr("class").split(" "));
1016 $snippets.each(function () {
1017 var len = _.intersection(_.compact(this.className.split(" ")), className).length;
1023 var $clone = $(selection).find('.item:first').clone();
1026 $clone.removeClass('active').insertAfter($active);
1027 setTimeout(function() {
1028 self.$target.carousel().carousel(++index);
1029 self.rebind_event();
1033 remove_slide: function (type, value) {
1034 if(type !== "click") return;
1036 if (this.remove_process) {
1041 var cycle = this.$inner.find('.item').length - 1;
1042 var index = this.$inner.find('.item.active').index();
1045 this.remove_process = true;
1046 var $el = this.$inner.find('.item.active');
1047 self.$target.on('slid.bs.carousel', function (event) {
1049 self.$indicators.find("li:last").remove();
1050 self.$target.off('slid.bs.carousel');
1051 self.rebind_event();
1052 self.remove_process = false;
1054 self.on_remove_slide(event);
1057 setTimeout(function () {
1058 self.$target.carousel( index > 0 ? --index : cycle );
1061 this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
1064 interval : function(type, value) {
1065 this.$target.attr("data-interval", value);
1067 set_active: function () {
1068 this.$el.find('li[data-interval]').removeClass("active")
1069 .filter('li[data-interval='+this.$target.attr("data-interval")+']').addClass("active");
1072 website.snippet.options.carousel = website.snippet.options.slider.extend({
1073 getSize: function () {
1074 this.grid = this._super();
1078 clean_for_save: function () {
1080 this.$target.removeClass('oe_img_bg ' + this._class).css("background-image", "");
1082 load_style_options : function () {
1084 $(".snippet-option-size li[data-value='']").remove();
1086 start : function () {
1090 // set background and prepare to clean for save
1091 var add_class = function (c){
1092 if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c;
1093 return self._class || "";
1095 this.$target.on('slid.bs.carousel', function () {
1096 if(self.editor && self.editor.styles.background) {
1097 self.editor.styles.background.$target = self.$target.find(".item.active");
1098 self.editor.styles.background.set_active();
1100 self.$target.carousel("pause");
1102 this.$target.trigger('slid.bs.carousel');
1104 add_slide: function (type, data) {
1105 if(type !== "click") return;
1107 var $clone = this._super(type, data);
1108 // choose an other background
1109 var bg = this.$target.data("snippet-option-ids").background;
1110 if (!bg) return $clone;
1112 var $styles = bg.$el.find("li[data-background]");
1113 var $select = $styles.filter(".active").removeClass("active").next("li[data-background]");
1114 if (!$select.length) {
1115 $select = $styles.first();
1117 $select.addClass("active");
1118 $clone.css("background-image", $select.data("background") ? "url('"+ $select.data("background") +"')" : "");
1122 // rebind event to active carousel on edit mode
1123 rebind_event: function () {
1125 this.$target.find('.carousel-control').off('click').on('click', function () {
1126 self.$target.carousel( $(this).data('slide')); });
1129 /* Fix: backward compatibility saas-3 */
1130 this.$target.find('.item.text_image, .item.image_text, .item.text_only').find('.container > .carousel-caption > div, .container > img.carousel-image').attr('contentEditable', 'true');
1133 website.snippet.options.marginAndResize = website.snippet.Option.extend({
1134 start: function () {
1138 var resize_values = this.getSize();
1139 if (resize_values.n) this.$overlay.find(".oe_handle.n").removeClass("readonly");
1140 if (resize_values.s) this.$overlay.find(".oe_handle.s").removeClass("readonly");
1141 if (resize_values.e) this.$overlay.find(".oe_handle.e").removeClass("readonly");
1142 if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly");
1143 if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly");
1145 this.$overlay.find(".oe_handle:not(.size), .oe_handle.size .size").on('mousedown', function (event){
1146 event.preventDefault();
1148 var $handle = $(this);
1150 var resize_values = self.getSize();
1151 var compass = false;
1153 if ($handle.hasClass('n')) {
1157 else if ($handle.hasClass('s')) {
1161 else if ($handle.hasClass('e')) {
1165 else if ($handle.hasClass('w')) {
1169 else if ($handle.hasClass('size')) {
1174 var resize = resize_values[compass];
1175 if (!resize) return;
1178 if (compass === 'size') {
1179 var offset = self.$target.offset().top;
1180 if (self.$target.css("background").match(/rgba\(0, 0, 0, 0\)/)) {
1181 self.$target.addClass("resize_editor_busy");
1184 var xy = event['page'+XY];
1185 var current = resize[2] || 0;
1186 _.each(resize[0], function (val, key) {
1187 if (self.$target.hasClass(val)) {
1191 var begin = current;
1192 var beginClass = self.$target.attr("class");
1193 var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g');
1196 self.BuildingBlock.editor_busy = true;
1198 var cursor = $handle.css("cursor")+'-important';
1199 var $body = $(document.body);
1200 $body.addClass(cursor);
1202 var body_mousemove = function (event){
1203 event.preventDefault();
1204 if (compass === 'size') {
1205 var dy = event.pageY-offset;
1206 dy = dy - dy%resize;
1207 if (dy <= 0) dy = resize;
1208 self.$target.css("height", dy+"px");
1209 self.$target.css("overflow", "hidden");
1210 self.on_resize(compass, null, dy);
1211 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1214 var dd = event['page'+XY] - xy + resize[1][begin];
1215 var next = current+1 === resize[1].length ? current : (current+1);
1216 var prev = current ? (current-1) : 0;
1219 if (dd > (2*resize[1][next] + resize[1][current])/3) {
1220 self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
1221 self.$target.addClass(resize[0][next]);
1225 if (prev != current && dd < (2*resize[1][prev] + resize[1][current])/3) {
1226 self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
1227 self.$target.addClass(resize[0][prev]);
1233 self.on_resize(compass, beginClass, current);
1234 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1238 var body_mouseup = function(){
1239 $body.unbind('mousemove', body_mousemove);
1240 $body.unbind('mouseup', body_mouseup);
1241 $body.removeClass(cursor);
1242 self.BuildingBlock.editor_busy = false;
1243 self.$target.removeClass("resize_editor_busy");
1245 $body.mousemove(body_mousemove);
1246 $body.mouseup(body_mouseup);
1248 this.$overlay.find(".oe_handle.size .auto_size").on('click', function (event){
1249 self.$target.css("height", "");
1250 self.$target.css("overflow", "");
1251 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1255 getSize: function () {
1260 on_focus : function () {
1262 this.change_cursor();
1265 change_cursor : function () {
1266 var _class = this.$target.attr("class") || "";
1268 var col = _class.match(/col-md-([0-9-]+)/i);
1269 col = col ? +col[1] : 0;
1271 var offset = _class.match(/col-md-offset-([0-9-]+)/i);
1272 offset = offset ? +offset[1] : 0;
1274 var overlay_class = this.$overlay.attr("class").replace(/(^|\s+)block-[^\s]*/gi, '');
1275 if (col+offset >= 12) overlay_class+= " block-e-right";
1276 if (col === 1) overlay_class+= " block-w-right block-e-left";
1277 if (offset === 0) overlay_class+= " block-w-left";
1279 var mb = _class.match(/mb([0-9-]+)/i);
1280 mb = mb ? +mb[1] : 0;
1281 if (mb >= 128) overlay_class+= " block-s-bottom";
1282 else if (!mb) overlay_class+= " block-s-top";
1284 var mt = _class.match(/mt([0-9-]+)/i);
1285 mt = mt ? +mt[1] : 0;
1286 if (mt >= 128) overlay_class+= " block-n-top";
1287 else if (!mt) overlay_class+= " block-n-bottom";
1289 this.$overlay.attr("class", overlay_class);
1293 * called when the box is resizing and the class change, before the cover_target
1294 * @compass: resize direction : 'n', 's', 'e', 'w'
1295 * @beginClass: attributes class at the begin
1296 * @current: curent increment in this.grid
1298 on_resize: function (compass, beginClass, current) {
1299 this.change_cursor();
1302 website.snippet.options["margin-y"] = website.snippet.options.marginAndResize.extend({
1303 getSize: function () {
1304 this.grid = this._super();
1305 var grid = [0,4,8,16,32,48,64,92,128];
1307 // list of class (Array), grid (Array), default value (INT)
1308 n: [_.map(grid, function (v) {return 'mt'+v;}), grid],
1309 s: [_.map(grid, function (v) {return 'mb'+v;}), grid],
1310 // INT if the user can resize the snippet (resizing per INT px)
1316 website.snippet.options["margin-x"] = website.snippet.options.marginAndResize.extend({
1317 getSize: function () {
1318 this.grid = this._super();
1319 var width = this.$target.parents(".row:first").first().outerWidth();
1321 var grid = [1,2,3,4,5,6,7,8,9,10,11,12];
1322 this.grid.e = [_.map(grid, function (v) {return 'col-md-'+v;}), _.map(grid, function (v) {return width/12*v;})];
1324 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];
1325 this.grid.w = [_.map(grid, function (v) {return 'col-md-offset-'+v;}), _.map(grid, function (v) {return width/12*v;}), 12];
1329 _drag_and_drop_after_insert_dropzone: function(){
1331 var $zones = $(".row:has(> .oe_drop_zone)").each(function () {
1333 var width = $row.innerWidth();
1335 while (width > pos + self.size.width) {
1336 var $last = $row.find("> .oe_drop_zone:last");
1337 $last.each(function () {
1338 pos = $(this).position().left;
1340 if (width > pos + self.size.width) {
1341 $row.append("<div class='col-md-1 oe_drop_to_remove'/>");
1342 var $add_drop = $last.clone();
1343 $row.append($add_drop);
1344 self._drag_and_drop_active_drop_zone($add_drop);
1349 _drag_and_drop_start: function () {
1351 this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''));
1353 _drag_and_drop_stop: function () {
1354 this.$target.addClass("col-md-offset-" + this.$target.prevAll(".oe_drop_to_remove").length);
1357 hide_remove_button: function() {
1358 this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length);
1360 on_focus : function () {
1362 this.hide_remove_button();
1364 on_clone: function ($clone) {
1365 var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, '');
1366 $clone.attr("class", _class);
1367 this.hide_remove_button();
1370 on_remove: function () {
1372 this.hide_remove_button();
1374 on_resize: function (compass, beginClass, current) {
1375 if (compass === 'w') {
1376 // don't change the right border position when we change the offset (replace col size)
1377 var beginCol = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0);
1378 var beginOffset = Number(beginClass.match(/col-md-offset-([0-9-]+)|$/)[1] || beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0);
1379 var offset = Number(this.grid.w[0][current].match(/col-md-offset-([0-9-]+)|$/)[1] || 0);
1383 var colSize = beginCol - (offset - beginOffset);
1386 offset = beginOffset + beginCol - 1;
1388 this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-|col-md-)([0-9-]+)/g, ''));
1390 this.$target.addClass('col-md-' + (colSize > 12 ? 12 : colSize));
1392 this.$target.addClass('col-md-offset-' + offset);
1395 this._super(compass, beginClass, current);
1399 website.snippet.options.resize = website.snippet.options.marginAndResize.extend({
1400 getSize: function () {
1401 this.grid = this._super();
1407 website.snippet.options.parallax = website.snippet.Option.extend({
1408 getSize: function () {
1409 this.grid = this._super();
1413 on_resize: function (compass, beginClass, current) {
1414 this.$target.data("snippet-view").set_values();
1416 start : function () {
1419 if (!self.$target.data("snippet-view")) {
1420 this.$target.data("snippet-view", new website.snippet.animationRegistry.parallax(this.$target));
1423 this.$target.on('snippet-option-change snippet-option-preview', function () {
1424 self.$target.data("snippet-view").set_values();
1426 this.$target.attr('contentEditable', 'false');
1428 this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); // saas-3 retro-compatibility
1430 this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true');
1432 scroll: function (type, value) {
1433 this.$target.attr('data-scroll-background-ratio', value);
1434 this.$target.data("snippet-view").set_values();
1436 set_active: function () {
1437 var value = this.$target.attr('data-scroll-background-ratio') || 0;
1438 this.$el.find('[data-scroll]').removeClass("active")
1439 .filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass("active");
1441 clean_for_save: function () {
1443 this.$target.find(".parallax")
1444 .css("background-position", '')
1445 .removeAttr("data-scroll-background-offset");
1449 website.snippet.options.transform = website.snippet.Option.extend({
1450 start: function () {
1453 this.$overlay.find('.oe_snippet_clone, .oe_handles').addClass('hidden');
1454 this.$overlay.find('[data-toggle="dropdown"]')
1455 .on("mousedown", function () {
1456 self.$target.transfo("hide");
1459 style: function (type, value) {
1460 if (type !== 'click') return;
1461 var settings = this.$target.data("transfo").settings;
1462 this.$target.transfo({ hide: (settings.hide = !settings.hide) });
1464 clear_style: function (type, value) {
1465 if (type !== 'click') return;
1466 this.$target.removeClass("fa-spin").attr("style", "");
1467 this.resetTransfo();
1469 resetTransfo: function () {
1471 this.$target.transfo("destroy");
1472 this.$target.transfo({
1474 callback: function () {
1475 var center = $(this).data("transfo").$markup.find('.transfo-scaler-mc').offset();
1476 var $option = self.$overlay.find('.btn-group:first');
1478 'top': center.top - $option.height()/2,
1479 'left': center.left,
1480 'position': 'absolute',
1482 self.$overlay.find(".oe_overlay_options").attr("style", "width:0; left:0!important; top:0;");
1483 self.$overlay.find(".oe_overlay_options > .btn-group").attr("style", "width:160px; left:-80px;");
1485 this.$target.data('transfo').$markup
1486 .on("mouseover", function () {
1487 self.$target.trigger("mouseover");
1491 on_focus : function () {
1492 this.resetTransfo();
1494 on_blur : function () {
1495 this.$target.transfo("hide");
1499 website.snippet.options.media = website.snippet.Option.extend({
1500 start: function () {
1504 website.snippet.start_animation(true, this.$target);
1506 $(document.body).on("media-saved", self, function (event, prev , item) {
1507 self.editor.on_blur();
1508 self.BuildingBlock.make_active(false);
1509 if (self.$target.parent().data("oe-field") !== "image") {
1510 self.BuildingBlock.make_active($(item));
1514 edition: function (type, value) {
1515 if(type !== "click") return;
1516 this.element = new CKEDITOR.dom.element(this.$target[0]);
1517 new website.editor.MediaDialog(this, this.element).appendTo(document.body);
1519 on_focus : function () {
1521 if (this.$target.parent().data("oe-field") === "image") {
1522 this.$overlay.addClass("hidden");
1523 self.element = new CKEDITOR.dom.element(self.$target[0]);
1524 new website.editor.MediaDialog(self, self.element).appendTo(document.body);
1525 self.BuildingBlock.make_active(false);
1527 setTimeout(function () {
1528 self.$target.find(".css_editable_mode_display").removeAttr("_moz_abspos");
1533 website.snippet.Editor = openerp.Class.extend({
1534 init: function (BuildingBlock, dom) {
1535 this.BuildingBlock = BuildingBlock;
1536 this.$target = $(dom);
1537 this.$overlay = this.$target.data('overlay');
1538 this.load_style_options();
1539 this.get_parent_block();
1543 // activate drag and drop for the snippets in the snippet toolbar
1544 _drag_and_drop: function(){
1546 this.dropped = false;
1547 this.$overlay.draggable({
1551 handle: ".oe_snippet_move",
1556 helper: function() {
1557 var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0});
1558 $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove();
1559 $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0});
1560 $clone.appendTo("body").removeClass("hidden");
1563 start: _.bind(self._drag_and_drop_start, self),
1564 stop: _.bind(self._drag_and_drop_stop, self)
1567 _drag_and_drop_after_insert_dropzone: function (){},
1568 _drag_and_drop_active_drop_zone: function ($zones){
1572 $(".oe_drop_zone.hide").removeClass("hide");
1573 $(this).addClass("hide").first().after(self.$target);
1574 self.dropped = true;
1577 $(this).removeClass("hide");
1578 self.$target.detach();
1579 self.dropped = false;
1583 _drag_and_drop_start: function (){
1585 self.BuildingBlock.hide();
1586 self.BuildingBlock.editor_busy = true;
1588 width: self.$target.width(),
1589 height: self.$target.height()
1591 self.$target.after("<div class='oe_drop_clone' style='display: none;'/>");
1592 self.$target.detach();
1593 self.$overlay.addClass("hidden");
1595 self.BuildingBlock.activate_insertion_zones({
1596 siblings: self.selector_siblings,
1597 children: self.selector_children,
1600 $("body").addClass('move-important');
1602 self._drag_and_drop_after_insert_dropzone();
1603 self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
1605 _drag_and_drop_stop: function (){
1607 if (!self.dropped) {
1608 $(".oe_drop_clone").after(self.$target);
1610 self.$overlay.removeClass("hidden");
1611 $("body").removeClass('move-important');
1612 $('.oe_drop_zone').droppable('destroy').remove();
1613 $(".oe_drop_clone, .oe_drop_to_remove").remove();
1614 self.BuildingBlock.editor_busy = false;
1615 self.get_parent_block();
1616 setTimeout(function () {self.BuildingBlock.create_overlay(self.$target);},0);
1619 load_style_options: function () {
1621 var $styles = this.$overlay.find('.oe_options');
1622 var $ul = $styles.find('ul:first');
1624 this.selector_siblings = [];
1625 this.selector_children = [];
1626 _.each(website.snippet.templateOptions, function (val, option_id) {
1627 if (!self.$target.is(val.selector)) {
1630 if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
1631 if (val['drop-in']) self.selector_children.push(val['drop-in']);
1633 var option = val['option'];
1634 var Editor = website.snippet.options[option] || website.snippet.Option;
1635 var editor = self.styles[option] = new Editor(self.BuildingBlock, self, self.$target, option_id);
1636 $ul.append(editor.$el.addClass("snippet-option-" + option));
1638 this.selector_siblings = this.selector_siblings.join(",");
1639 if (this.selector_siblings === "")
1640 this.selector_siblings = false;
1641 this.selector_children = this.selector_children.join(",");
1642 if (this.selector_children === "")
1643 this.selector_children = false;
1645 if (!this.selector_siblings && !this.selector_children) {
1646 this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden');
1650 if ($ul.find("li").length) {
1651 $styles.removeClass("hidden");
1653 this.$overlay.find('[data-toggle="dropdown"]').dropdown();
1656 get_parent_block: function () {
1658 var $button = this.$overlay.find('.oe_snippet_parent');
1659 var $parent = this.$target.parents(website.snippet.globalSelector).first();
1660 if ($parent.length) {
1661 $button.removeClass("hidden");
1662 $button.off("click").on('click', function (event) {
1663 event.preventDefault();
1664 setTimeout(function () {
1665 self.BuildingBlock.make_active($parent);
1669 $button.addClass("hidden");
1675 * This method is called after init and _readXMLData
1677 start: function () {
1679 this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this));
1680 this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this));
1681 this._drag_and_drop();
1684 on_clone: function () {
1685 var $clone = this.$target.clone(false);
1686 this.$target.after($clone);
1687 for (var i in this.styles){
1688 this.styles[i].on_clone($clone);
1693 on_remove: function () {
1695 var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0));
1696 for (var i in this.styles){
1697 this.styles[i].on_remove();
1699 delete this.BuildingBlock.snippets[index];
1701 // remove node and his empty
1703 node = this.$target.parent()[0];
1705 this.$target.remove();
1706 function check(node) {
1707 if ($(node).outerHeight() > 8) {
1710 for (var k=0; k<node.children.length; k++) {
1711 if (node.children[k].tagName || node.children[k].textContent.match(/[^\s]/)) {
1717 while (check(node)) {
1718 parent = node.parentNode;
1719 parent.removeChild(node);
1723 this.$overlay.remove();
1728 * drop_and_build_snippet
1729 * This method is called just after that a thumbnail is drag and dropped into a drop zone
1730 * (after the insertion of this.$body, if this.$body exists)
1732 drop_and_build_snippet: function () {
1733 for (var i in this.styles){
1734 this.styles[i].drop_and_build_snippet();
1739 * This method is called when the user click inside the snippet in the dom
1741 on_focus : function () {
1742 this.$overlay.addClass('oe_active');
1743 for (var i in this.styles){
1744 this.styles[i].on_focus();
1749 * This method is called when the user click outside the snippet in the dom, after a focus
1751 on_blur : function () {
1752 for (var i in this.styles){
1753 this.styles[i].on_blur();
1755 this.$overlay.removeClass('oe_active');