[MERGE] forward port of branch 8.0 up to 92c7874
[odoo/odoo.git] / addons / website / static / src / js / website.snippets.editor.js
index 6b4afd0..90fbf3a 100644 (file)
@@ -1,135 +1,6 @@
 (function () {
     'use strict';
 
-/*  Building block / Snippet Editor
-    The building blocks appear in the edit bar website. These prebuilt html block
-    allowing the designer to easily generate content on a page (drag and drop).
-    Options allow snippets to add customizations part html code according to their
-    selector (jQuery) and javascript object.
-    
-    How to create content?
-
-    Designers can add their own html block in the "snippets" (/website/views/snippets.xml).
-    The block must be added in one of four menus (structure, content, feature or effect).
-    Structure:
-        <div>
-            <div class="oe_snippet_thumbnail">
-                <img class="oe_snippet_thumbnail_img" src="...image src..."/>
-                <span class="oe_snippet_thumbnail_title">...Block Name...</span>
-            </div>
-            <div class="oe_snippet_body">
-                ...
-                <!-- 
-                    The block with class 'oe_snippet_body' is inserted in the page.
-                    This class is removed when the block is dropped.
-                    The block can be made of any html tag and content. -->
-            </div>
-        </div>
-
-    How to create options?
-
-    Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml).
-    Structure:
-
-        <div data-snippet-option-id='...'           <!-- Required: javascript object id (but javascript
-                                                        for this option object is not required) -->
-            data-selector="..."                     <!-- Required: jQuery selector.
-                                                        Apply options on all The part of html who 
-                                                        match with this jQuery selector.
-                                                        E.g.: If the selector is div, all div will be selected
-                                                        and can be highlighted and assigned an editor.  -->
-            data-selector-siblings="..."            <!-- Optional: jQuery selector.
-                                                        The html part can be insert or move beside
-                                                        the selected html block -->
-            data-selector-children="..."            <!-- Optional: jQuery selector.
-                                                        The html part can be insert or move inside
-                                                        the selected html block -->
-            data-selector-vertical-children='...'>  <!-- Optional: jQuery selector.
-                                                        The html part can be insert or move inside
-                                                        the selected html block. The drop zone is
-                                                        displayed vertically -->
-                ...
-                <li><a href="#">...</a></li>        <!-- Optional: html li list.
-                                                        List of menu items displayed in customize
-                                                        menu. If the li tag have 'data-class', the
-                                                        class is automaticcally added or removed to
-                                                        the html content when the user select this item. -->
-                ...
-                <li class="dropdown-submenu"                <!-- Optional: html li list exemple. !-->
-                    data-required="true">                   <!-- Optional: if only one item can be selected
-                                                                and can't be unselect. !-->
-                    <a tabindex="-1" href="#">...</a>       <!-- bootstrap dropdown button !-->
-                    <ul class="dropdown-menu">
-                        <li data-value="text_only"><a>...</a></li>      <!-- by default data-value is apply
-                                                                            like a class to html block !-->
-                    </ul>
-                </li>
-        </div>
-
-        How to create a javascript object for an options?
-
-        openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({
-            // start is called when the user click into a block or when the user drop a block 
-            // into the page (just after the init method).
-            // start is usually used to bind event.
-            //
-            // this.$target: block html inserted inside the page
-            // this.$el: html li list of this options
-            // this.$overlay: html editor overlay who content resize bar, customize menu...
-            start: function () {},
-
-
-            // onFocus is called when the user click inside the block inserted in page
-            // and when the user drop on block into the page
-            onFocus : function () {},
-
-
-            // onBlur is called when the user click outside the block inserted in page, if
-            // the block is focused
-            onBlur : function () {},
-
-
-            // on_clone is called when the snippet is duplicate
-            // @variables: $clone is allready inserted is the page
-            on_clone: function ($clone) {},
-
-
-            // on_remove is called when the snippet is removed (dom is removing after this tigger)
-            on_remove: function () {},
-
-
-            // drop_and_build_snippet is called just after that a thumbnail is drag and dropped
-            // into a drop zone. The content is already inserted in the page.
-            drop_and_build_snippet: function () {},
-
-            // select is called when a user select an item in the li list of options
-            // By default, if the li item have a data-value attribute, the data-vlue it's apply
-            // like a class to the html block (this.$target)
-            // @variables: next_previous = {$next, $prev}
-            //      $next = next item selected or false
-            //      $prev = previous item selected or false
-            select: function (event, next_previous) {}
-
-            // preview is called when a user is on mouse over or mouse out of an item
-            // variables: next_previous = {$next, $prev}
-            //      $next = next item selected or false
-            //      $prev = previous item selected or false
-            preview: function (event, next_previous) {}
-
-            // clean_for_save
-            // clean_for_save is called just before to save the vue
-            // Sometime it's important to remove or add some datas (contentEditable, added 
-            // classes to a running animation...)
-            clean_for_save: function () {}
-        });
-
-
-    // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped
-    // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated
-
-*/
-
     var dummy = function () {};
 
     var website = openerp.website;
         }
     });
 
+    $.extend($.expr[':'],{
+        checkData: function(node,i,m){
+            var dataName = m[3];
+            while (node) {
+                if (node.dataset && node.dataset[dataName]) {
+                    return true;
+                } else {
+                    node = node.parentNode;
+                }
+            }
+            return false;
+        },
+        hasData: function(node,i,m){
+            return !!_.toArray(node.dataset).length;
+        },
+    });
+
     if (!website.snippet) website.snippet = {};
-    website.snippet.templateOptions = {};
+    website.snippet.templateOptions = [];
     website.snippet.globalSelector = "";
     website.snippet.selector = [];
     website.snippet.BuildingBlock = openerp.Widget.extend({
                 subtree: true,
             });
         },
-        dom_filter: function (dom, sibling) {
-            if (typeof dom === "string") {
-                var include = "[data-oe-model]";
-                var sdom = dom.split(',');
-                dom = "";
-                _.each(sdom, function (val) {
-                    val = val.replace(/^\s+|\s+$/g, '');
-                    dom += include + " " + val + ", ";
-                    if (!sibling) {
-                        val = val.split(" ");
-                        dom += val.shift() + include + val.join(" ") + ", ";
-                    }
-                });
-                dom = dom.replace(/,\s*$/g, '');
-                return $(dom);
-            } else {
-                return (!sibling && $(dom).is("[data-oe-model]")) || $(dom).parents("[data-oe-model]").length ? $(dom) : $("");
-            }
-        },
         start: function() {
             var self = this;
 
                 .prependTo(this.parent.$("#website-top-edit ul"))
                 .find("button");
 
-            this.$button.click(function () {
-                self.make_active(false);
-                self.$el.toggleClass("hidden");
-            });
-            $("#wrapwrap").click(function () {
+            this.$button.click(_.bind(this.show_blocks, this));
+
+            this.$snippet = $("#oe_snippets");
+            this.$wrapwrap = $("#wrapwrap");
+            this.$wrapwrap.click(function () {
                 self.$el.addClass("hidden");
             });
 
             this.fetch_snippet_templates();
-
             this.bind_snippet_click_editor();
-
             this.$el.addClass("hidden");
 
             $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
             this.getParent().on('change:height', this, function (editor) {
                 self.$el.css('top', editor.get('height'));
             });
-            self.$el.css('top', this.parent.get('height'));
+            this.$el.css('top', this.parent.get('height'));
+        },
+        show_blocks: function () {
+            var self = this;
+            this.make_active(false);
+            this.$el.toggleClass("hidden");
+            if (this.$el.hasClass("hidden")) {
+                return;
+            }
+
+            //this.enable_snippets( this.$snippet.find(".tab-pane.active") );
+            var categories = this.$snippet.find(".tab-pane.active")
+                .add(this.$snippet.find(".tab-pane:not(.active)"))
+                .get().reverse();
+            function enable() {
+                self.enable_snippets( $(categories.pop()) );
+                if (categories.length) {
+                    setTimeout(enable,10);
+                }
+            }
+            setTimeout(enable,0);
+        },
+        enable_snippets: function ($category) {
+            var self = this;
+            $category.find(".oe_snippet_body").each(function () {
+                var $snippet = $(this);
+
+                if (!$snippet.data('selectors')) {
+                    var selectors = [];
+                    for (var k in website.snippet.templateOptions) {
+                        var option = website.snippet.templateOptions[k];
+                        if ($snippet.is(option.base_selector)) {
+
+                            var dropzone = [];
+                            if (option['drop-near']) dropzone.push(option['drop-near']);
+                            if (option['drop-in']) dropzone.push(option['drop-in']);
+                            if (option['drop-in-vertical']) dropzone.push(option['drop-in-vertical']);
+                            selectors = selectors.concat(dropzone);
+                        }
+                    }
+                    $snippet.data('selectors', selectors.length ? selectors.join(":first, ") + ":first" : "");
+                }
+
+                if ($snippet.data('selectors').length && self.$wrapwrap.find($snippet.data('selectors')).size()) {
+                    $snippet.closest(".oe_snippet").removeClass("disable");
+                } else {
+                    $snippet.closest(".oe_snippet").addClass("disable");
+                }
+            });
+            $('#oe_snippets .scroll a[data-toggle="tab"][href="#' + $category.attr("id") + '"]')
+                .toggle(!!$category.find(".oe_snippet:not(.disable)").size());
         },
         _get_snippet_url: function () {
             return '/website/snippets';
         },
+        _add_check_selector : function (selector, no_check) {
+            var data = selector.split(",");
+            var selectors = [];
+            for (var k in data) {
+                selectors.push(data[k].replace(/^\s+|\s+$/g, '') + (no_check ? "" : ":checkData(oeModel)"));
+            }
+            return selectors.join(", ");
+        },
         fetch_snippet_templates: function () {
             var self = this;
 
                     var $html = $(html);
 
                     var selector = [];
-                    var $styles = $html.find("[data-snippet-option-id]");
+                    var $styles = $html.find("[data-js], [data-selector]");
                     $styles.each(function () {
                         var $style = $(this);
-                        var style_id = $style.data('snippet-option-id');
-                        website.snippet.templateOptions[style_id] = {
-                            'snippet-option-id' : style_id,
-                            'selector': $style.data('selector'),
+                        var no_check = $style.data('no-check');
+                        var option_id = $style.data('js');
+                        var option = {
+                            'option' : option_id,
+                            'base_selector': $style.data('selector'),
+                            'selector': self._add_check_selector($style.data('selector'), no_check),
                             '$el': $style,
-                            'selector-siblings': $style.data('selector-siblings'),
-                            'selector-children': $style.data('selector-children'),
-                            'selector-vertical-children': $style.data('selector-vertical-children'),
+                            'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check),
+                            'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check),
                             'data': $style.data()
                         };
-                        selector.push($style.data('selector'));
+                        website.snippet.templateOptions.push(option);
+                        selector.push(option.selector);
                     });
                     $styles.addClass("hidden");
                     website.snippet.globalSelector = selector.join(",");
 
-                    self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet");
-                    self.$el.append($html);
-
+                    self.$snippets = $html.find(".tab-content > div > div")
+                        .addClass("oe_snippet")
+                        .each(function () {
+                            if (!$('.oe_snippet_thumbnail', this).size()) {
+                                var $div = $(
+                                    '<div class="oe_snippet_thumbnail">'+
+                                        '<div class="oe_snippet_thumbnail_img"/>'+
+                                        '<span class="oe_snippet_thumbnail_title"></span>'+
+                                    '</div>');
+                                $div.find('span').text($(this).attr("name"));
+                                $(this).prepend($div);
+                            }
+                            $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
+                        });
 
-                    var snippets = 0;
-                    self.$snippets.each(function () {
-                        if (self.snippet_have_dropzone($(this)))
-                            snippets++;
-                    });
-                    if (!snippets) self.$button.css("display", "none");
+                    self.$el.append($html);
 
                     self.make_snippet_draggable(self.$snippets);
                 });
         hide: function () {
             this.$el.addClass("hidden");
         },
-
-        snippet_have_dropzone: function ($snippet) {
-            return (($snippet.data('selector-siblings') && this.dom_filter($snippet.data('selector-siblings')).size() > 0) ||
-                    ($snippet.data('selector-children') && this.dom_filter($snippet.data('selector-children')).size() > 0) ||
-                    ($snippet.data('selector-vertical-children') && this.dom_filter($snippet.data('selector-vertical-children')).size() > 0));
-        },
-
         bind_snippet_click_editor: function () {
             var self = this;
             var snipped_event_flag;
-            $("#wrapwrap").on('click', function (event) {
+            self.$wrapwrap.on('click', function (event) {
                 var srcElement = event.srcElement || (event.originalEvent && (event.originalEvent.originalTarget || event.originalEvent.target));
                 if (snipped_event_flag || !srcElement) {
                     return;
                     $target = $target.parents(website.snippet.globalSelector).first();
                 }
 
-                if (!self.dom_filter($target).length) {
-                    $target = false;
-                }
                 if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
                     return;
                 }
         snippet_blur: function ($snippet) {
             if ($snippet) {
                 if ($snippet.data("snippet-editor")) {
-                    $snippet.data("snippet-editor").onBlur();
+                    $snippet.data("snippet-editor").on_blur();
                 }
             }
         },
         snippet_focus: function ($snippet) {
             if ($snippet) {
                 if ($snippet.data("snippet-editor")) {
-                    $snippet.data("snippet-editor").onFocus();
+                    $snippet.data("snippet-editor").on_focus();
                 }
             }
         },
             var self = this;
             var options = website.snippet.options;
             var template = website.snippet.templateOptions;
-            for (var k in options) {
-                if (template[k] && options[k].prototype.clean_for_save !== dummy) {
-                    var $snippet = this.dom_filter(template[k].selector);
-                    $snippet.each(function () {
-                        new options[k](self, null, $(this), k).clean_for_save();
+            for (var k in template) {
+                var Option = options[template[k]['option']];
+                if (Option && Option.prototype.clean_for_save !== dummy) {
+                    self.$wrapwrap.find(template[k].selector).each(function () {
+                        new Option(self, null, $(this), k).clean_for_save();
                     });
                 }
             }
-            $("*[contentEditable], *[attributeEditable]")
+            self.$wrapwrap.find("*[contentEditable], *[attributeEditable]")
                 .removeAttr('contentEditable')
                 .removeAttr('attributeEditable');
         },
                 this.create_overlay(this.$active_snipped_id);
                 this.snippet_focus($snippet);
             }
-            $("#oe_snippets").trigger('snippet-activated', $snippet);
+            this.$snippet.trigger('snippet-activated', $snippet);
             if ($snippet) {
                 $snippet.trigger('snippet-activated', $snippet);
             }
             this.cover_target($snippet.data('overlay'), $snippet);
         },
 
-        path_eval: function(path){
-            var obj = window;
-            path = path.split('.');
-            do{
-                obj = obj[path.shift()];
-            }while(path.length && obj);
-            return obj;
-        },
-
         // activate drag and drop for the snippets in the snippet toolbar
         make_snippet_draggable: function($snippets){
             var self = this;
-            var $tumb = $snippets.find(".oe_snippet_thumbnail:first");
+            var $tumb = $snippets.find(".oe_snippet_thumbnail_img:first");
             var left = $tumb.outerWidth()/2;
             var top = $tumb.outerHeight()/2;
             var $toInsert, dropped, $snippet, action, snipped_id;
                 start: function(){
                     self.hide();
                     dropped = false;
-                    // snippet_selectors => to get selector-siblings, selector-children, selector-vertical-children
+                    // snippet_selectors => to get drop-near, drop-in
                     $snippet = $(this);
-                    $toInsert = $snippet.find('.oe_snippet_body').clone();
-
+                    var $base_body = $snippet.find('.oe_snippet_body');
                     var selector = [];
                     var selector_siblings = [];
                     var selector_children = [];
-                    var selector_vertical_children = [];
-                    for (var k in website.snippet.templateOptions) {
-                        if ($toInsert.is(website.snippet.templateOptions[k].selector)) {
-                            selector.push(website.snippet.templateOptions[k].selector);
-                            if (website.snippet.templateOptions[k]['selector-siblings'])
-                                selector_siblings.push(website.snippet.templateOptions[k]['selector-siblings']);
-                            if (website.snippet.templateOptions[k]['selector-children'])
-                                selector_children.push(website.snippet.templateOptions[k]['selector-children']);
-                            if (website.snippet.templateOptions[k]['selector-vertical-children'])
-                                selector_vertical_children.push(website.snippet.templateOptions[k]['selector-vertical-children']);
+                    var vertical = false;
+                    var temp = website.snippet.templateOptions;
+                    for (var k in temp) {
+                        if ($base_body.is(temp[k].base_selector)) {
+                            selector.push(temp[k].base_selector);
+                            if (temp[k]['drop-near'])
+                                selector_siblings.push(temp[k]['drop-near']);
+                            if (temp[k]['drop-in'])
+                                selector_children.push(temp[k]['drop-in']);
                         }
                     }
 
+                    $toInsert = $base_body.clone();
                     action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate';
 
                     if( action === 'insert'){
-                        if (!selector_siblings.length && !selector_children.length && !selector_vertical_children.length) {
-                            console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+
-                                "data-selector-siblings, data-selector-children or data-selector-vertical-children tag for mutate action");
+                        if (!selector_siblings.length && !selector_children.length) {
+                            console.debug($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
                             return;
                         }
                         self.activate_insertion_zones({
                             siblings: selector_siblings.join(","),
                             children: selector_children.join(","),
-                            vertical_children: selector_vertical_children.join(","),
                         });
 
                     } else if( action === 'mutate' ){
                         if (!$snippet.data('selector')) {
-                            console.debug($snippet.data("snippet-id") + " have not oe_snippet_body class and have not data-selector tag");
+                            console.debug($snippet.data("option") + " have not oe_snippet_body class and have not data-selector tag");
                             return;
                         }
                         var $targets = self.activate_overlay_zones(selector_children.join(","));
                 stop: function(ev, ui){
                     $toInsert.removeClass('oe_snippet_body');
                     
-                    if (action === 'insert' && ! dropped && $('.oe_drop_zone') && ui.position.top > 3) {
-                        var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
+                    if (action === 'insert' && ! dropped && self.$wrapwrap.find('.oe_drop_zone') && ui.position.top > 3) {
+                        var el = self.$wrapwrap.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
                         if (el.length) {
                             el.after($toInsert);
                             dropped = true;
                         }
                     }
 
-                    $('.oe_drop_zone').droppable('destroy').remove();
+                    self.$wrapwrap.find('.oe_drop_zone').droppable('destroy').remove();
                     
                     if (dropped) {
                         var $target = false;
                         $target = $toInsert;
 
                         setTimeout(function () {
-                            $("#oe_snippets").trigger('snippet-dropped', $target);
+                            self.$snippet.trigger('snippet-dropped', $target);
 
                             website.snippet.start_animation(true, $target);
-                            // drop_and_build_snippet
-                            self.create_overlay($target);
-                            if ($target.data("snippet-editor")) {
-                                $target.data("snippet-editor").drop_and_build_snippet();
-                            }
-                            for (var k in website.snippet.templateOptions) {
-                                $target.find(website.snippet.templateOptions[k].selector).each(function () {
-                                    var $snippet = $(this);
-                                    self.create_overlay($snippet);
-                                    if ($snippet.data("snippet-editor")) {
-                                        $snippet.data("snippet-editor").drop_and_build_snippet();
-                                    }
-                                });
-                            }
 
                             // reset snippet for rte
                             $target.removeData("snippet-editor");
                                     $snippet.removeData("overlay");
                                 }
                             });
+                            // end
+
+                            // drop_and_build_snippet
                             self.create_overlay($target);
+                            if ($target.data("snippet-editor")) {
+                                $target.data("snippet-editor").drop_and_build_snippet();
+                            }
+                            for (var k in website.snippet.templateOptions) {
+                                $target.find(website.snippet.templateOptions[k].selector).each(function () {
+                                    var $snippet = $(this);
+                                    self.create_overlay($snippet);
+                                    if ($snippet.data("snippet-editor")) {
+                                        $snippet.data("snippet-editor").drop_and_build_snippet();
+                                    }
+                                });
+                            }
                             // end
 
                             self.make_active($target);
         // return the original snippet in the editor bar from a snippet id (string)
         get_snippet_from_id: function(id){
             return $('.oe_snippet').filter(function(){
-                    return $(this).data('snippet-id') === id;
+                    return $(this).data('option') === id;
                 }).first();
         },
 
             var self = this;
             var child_selector = selector.children;
             var sibling_selector = selector.siblings;
-            var vertical_child_selector   =  selector.vertical_children;
 
-            var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
+            var zone_template = $("<div class='oe_drop_zone oe_insert'></div>");
 
             if(child_selector){
-                self.dom_filter(child_selector).each(function (){
-                    var $zone = $(this);
-                    $zone.find('> *:not(.oe_drop_zone):visible').after(zone_template);
-                    $zone.prepend(zone_template);
-                });
-            }
-
-            if(vertical_child_selector){
-                self.dom_filter(vertical_child_selector).each(function (){
+                self.$wrapwrap.find(child_selector).each(function (){
                     var $zone = $(this);
-                    var $template = $(zone_template).addClass("oe_vertical");
-                    var nb = 0;
-                    var $lastinsert = false;
-                    var left = 0;
-                    var temp_left = 0;
-                    $zone.find('> *:not(.oe_drop_zone):visible').each(function () {
-                        var $col = $(this);
-                        $template.css('height', ($col.outerHeight() + parseInt($col.css("margin-top")) + parseInt($col.css("margin-bottom")))+'px');
-                        $lastinsert = $template.clone();
-                        $(this).after($lastinsert);
-
-                        temp_left = $col.position().left;
-                        if (left === temp_left) {
-                            $col.prev(".oe_drop_zone.oe_vertical").remove();
-                            $col.before($template.clone().css("clear", "left"));
-                        }
-                        else if (!nb) {
-                            $col.before($template.clone());
-                        }
-                        left = temp_left;
-                        nb ++;
-                    });
-                    if (!nb) {
-                        $zone.prepend($template.css('height', $zone.outerHeight()+'px'));
+                    var vertical;
+                    var float = window.getComputedStyle(this).float;
+                    if (float === "left" || float === "right") {
+                        vertical = $zone.parent().outerHeight()+'px';
+                    }
+                    var $drop = zone_template.clone();
+                    if (vertical) {
+                        $drop.addClass("oe_vertical").css('height', vertical);
                     }
+                    $zone.find('> *:not(.oe_drop_zone):visible').after($drop);
+                    $zone.prepend($drop.clone());
                 });
             }
 
             if(sibling_selector){
-                self.dom_filter(sibling_selector, true).each(function (){
+                self.$wrapwrap.find(sibling_selector, true).each(function (){
                     var $zone = $(this);
+                    var $drop, vertical;
+                    var float = window.getComputedStyle(this).float;
+                    if (float === "left" || float === "right") {
+                        vertical = $zone.parent().outerHeight()+'px';
+                    }
+
                     if($zone.prev('.oe_drop_zone:visible').length === 0){
-                        $zone.before(zone_template);
+                        $drop = zone_template.clone();
+                        if (vertical) {
+                            $drop.addClass("oe_vertical").css('height', vertical);
+                        }
+                        $zone.before($drop);
                     }
                     if($zone.next('.oe_drop_zone:visible').length === 0){
-                        $zone.after(zone_template);
+                        $drop = zone_template.clone();
+                        if (vertical) {
+                            $drop.addClass("oe_vertical").css('height', vertical);
+                        }
+                        $zone.after($drop);
                     }
                 });
             }
                 // count += $zones.length;
                 // $zones.remove();
 
-                $zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove();   // no recursive zones
+                $zones = self.$wrapwrap.find('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove();   // no recursive zones
                 count += $zones.length;
                 $zones.remove();
             } while (count > 0);
 
-            // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones.
-            var $zones = $('.oe_drop_zone:not(.oe_vertical)');
+            // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
+            var $zones = self.$wrapwrap.find('.oe_drop_zone:not(.oe_vertical)');
             $zones.each(function (){
                 var zone = $(this);
                 var prev = zone.prev();
                 var next = zone.next();
+                // remove consecutive zone
+                if (!zone.hasClass('.oe_vertical') && (prev.is('.oe_drop_zone:not(.oe_vertical)') || next.is('.oe_drop_zone:not(.oe_vertical)'))) {
+                    zone.remove();
+                    return;
+                }
                 var float_prev = prev.css('float')   || 'none';
                 var float_next = next.css('float')   || 'none';
                 var disp_prev  = prev.css('display') ||  null;
         // generate drop zones covering the elements selected by the selector
         // we generate overlay drop zones only to get an idea of where the snippet are, the drop
         activate_overlay_zones: function(selector){
-            var $targets = this.dom_filter(selector);
+            var $targets = typeof selector === "string" ? this.$wrapwrap.find(selector) : selector;
             var self = this;
 
-            if (typeof selector !== 'string' && !$targets.length) {
-                console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute.");
-                console.debug( "Wrong node(s): ", selector);
-            }
-
             function is_visible($el){
                 return     $el.css('display')    != 'none'
                         && $el.css('opacity')    != '0'
                     $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () {
                         self.cover_target($zone, $target);
                     });
-                    $('body').on("resize", function () {
-                        self.cover_target($zone, $target);
-                    });
+                    var resize = function () {
+                        if ($zone.parent().length) {
+                            self.cover_target($zone, $target);
+                        } else {
+                            $('body').off("resize", resize);
+                        }
+                    };
+                    $('body').on("resize", resize);
                 }
                 self.cover_target($target.data('overlay'), $target);
             });
     website.snippet.options = {};
     website.snippet.Option = openerp.Class.extend({
         // initialisation (don't overwrite)
-        init: function (BuildingBlock, editor, $target, snippet_id) {
+        init: function (BuildingBlock, editor, $target, option_id) {
             this.BuildingBlock = BuildingBlock;
             this.editor = editor;
             this.$target = $target;
+            var option = website.snippet.templateOptions[option_id];
             var styles = this.$target.data("snippet-option-ids") || {};
-            styles[snippet_id] = this;
+            styles[option_id] = this;
             this.$target.data("snippet-option-ids", styles);
             this.$overlay = this.$target.data('overlay') || $('<div>');
-            this['snippet-option-id'] = snippet_id;
-            var $option = website.snippet.templateOptions[snippet_id].$el;
-            this.$el = $option.find(">li").clone();
-            this.data = $option.data();
-
-            this.required = this.$el.data("required");
+            this.option= option_id;
+            this.$el = option.$el.find(">li").clone();
+            this.data = option.$el.data();
 
             this.set_active();
-            this.$el.find('li[data-value] a').on('mouseenter mouseleave click', _.bind(this._mouse, this));
-            this.$el.not(':not([data-value])').find("a").on('mouseenter mouseleave click', _.bind(this._mouse, this));
-            this.$target.on('snippet-style-reset', _.bind(this.set_active, this));
+            this.$target.on('snippet-option-reset', _.bind(this.set_active, this));
+            this._bind_li_menu();
 
             this.start();
         },
-        _mouse: function (event) {
-            var self = this;
 
-            if (event.type === 'mouseleave') {
-                if (!this.over) return;
-                this.over = false;
-            } else if (event.type === 'click') {
-                this.over = false;
-            }else {
-                this.over = true;
-            }
+        _bind_li_menu: function () {
+            this.$el.filter("li:hasData").find('a:first')
+                .off('mouseenter click')
+                .on('mouseenter click', _.bind(this._mouse, this));
 
-            var $prev, $next;
-            if (event.type === 'mouseleave') {
-                $prev = $(event.currentTarget).parent();
-                $next = this.$el.find("li[data-value].active");
-            } else {
-                $prev = this.$el.find("li[data-value].active");
-                $next = $(event.currentTarget).parent();
-            }
-            if (!$prev.length) {
-                $prev = false;
-            }
-            if ($prev && $prev[0] === $next[0]) {
-                $next = false;
-                if (this.required) {
-                    return;
-                }
-            }
+            this.$el
+                .off('mouseenter click', "li:hasData a")
+                .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this));
 
-            var np = {'$next': $next, '$prev': $prev};
+            this.$el.closest("ul").add(this.$el)
+                .off('mouseleave')
+                .on('mouseleave', _.bind(this.reset, this));
 
+            this.$el
+                .off('mouseleave', "ul")
+                .on('mouseleave', "ul", _.bind(this.reset, this));
+
+            this.reset_methods = [];
+            this.reset_time = null;
+        },
+
+        /**
+         * this method handles mouse:over and mouse:leave on the snippet editor menu
+         */
+         _time_mouseleave: null,
+        _mouse: function (event) {
+            var $next = $(event.currentTarget).parent();
+
+            // triggers preview or apply methods if a menu item has been clicked
+            this.select(event.type === "click" ? "click" : "over", $next);
             if (event.type === 'click') {
-                setTimeout(function () {
-                    self.set_active();
-                    self.$target.trigger("snippet-style-change", [self, np]);
-                },0);
-                this.select({'$next': $next, '$prev': $prev});
+                this.set_active();
+                this.$target.trigger("snippet-option-change", [this]);
             } else {
-                setTimeout(function () {
-                    self.$target.trigger("snippet-style-preview", [self, np]);
-                },0);
-                this.preview(np);
+                this.$target.trigger("snippet-option-preview", [this]);
             }
         },
-        /* set_active
+        /* 
         *  select and set item active or not (add highlight item and his parents)
         *  called before start
         */
         set_active: function () {
-            var self = this;
-            this.$el.find('li').removeClass("active");
-            var $active = this.$el.find('li[data-value]')
-                .filter(function () {
-                    var $li = $(this);
-                    return  ($li.data('value') && self.$target.hasClass($li.data('value')));
-                })
-                .first()
+            var classes = _.uniq((this.$target.attr("class") || '').split(/\s+/));
+            this.$el.find('[data-toggle_class], [data-select_class]')
+                .add(this.$el)
+                .filter('[data-toggle_class], [data-select_class]')
+                .removeClass("active")
+                .filter('[data-toggle_class="' + classes.join('"], [data-toggle_class="') + '"] ,'+
+                    '[data-select_class="' + classes.join('"], [data-select_class="') + '"]')
                 .addClass("active");
-            this.$el.find('li:has(li[data-value].active)').addClass("active");
         },
 
         start: function () {
         },
 
-        onFocus : function () {
+        on_focus : function () {
+            this._bind_li_menu();
         },
 
-        onBlur : function () {
+        on_blur : function () {
         },
 
         on_clone: function ($clone) {
         drop_and_build_snippet: function () {
         },
 
-        select: function (np) {
+        reset: function (event) {
             var self = this;
-            // add or remove html class
-            if (np.$prev && this.required) {
-                this.$target.removeClass(np.$prev.data('value' || ""));
+            var lis = self.$el.add(self.$el.find('li')).filter('.active').get();
+            lis.reverse();
+            _.each(lis, function (li) {
+                var $li = $(li);
+                for (var k in self.reset_methods) {
+                    var method = self.reset_methods[k];
+                    if ($li.is('[data-'+method+']') || $li.closest('[data-'+method+']').size()) {
+                        delete self.reset_methods[k];
+                    }
+                }
+                self.select("reset", $li);
+            });
+
+            for (var k in self.reset_methods) {
+                var method = self.reset_methods[k];
+                if (method) {
+                    self[method]("reset", null);
+                }
+            }
+            self.reset_methods = [];
+            self.$target.trigger("snippet-option-reset", [this]);
+        },
+
+        // call data-method args as method
+        select: function (type, $li) {
+            var self = this,
+                $methods = [],
+                el = $li[0],
+                $el;
+            clearTimeout(this.reset_time);
+
+            function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId';}
+            function hasData(el) {
+                for (var k in el.dataset) {
+                    if (filter (k)) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+            function method(el) {
+                var data = {};
+                for (var k in el.dataset) {
+                    if (filter (k)) {
+                        data[k] = el.dataset[k];
+                    }
+                }
+                return data;
             }
-            if (np.$next) {
-                this.$target.addClass(np.$next.data('value') || "");
+
+            while (el && this.$el.is(el) || _.some(this.$el.map(function () {return $.contains(this, el);}).get()) ) {
+                if (hasData(el)) {
+                    $methods.push(el);
+                }
+                el = el.parentNode;
             }
+
+            $methods.reverse();
+
+            _.each($methods, function (el) {
+                var $el = $(el);
+                var methods = method(el);
+
+                for (var k in methods) {
+                    if (self[k]) {
+                        if (type !== "reset" && self.reset_methods.indexOf(k) === -1) {
+                            self.reset_methods.push(k);
+                        }
+                        self[k](type, methods[k], $el);
+                    } else {
+                        console.error("'"+self.option+"' snippet have not method '"+k+"'");
+                    }
+                }
+            });
         },
 
-        preview: function (np) {
-            var self = this;
+        // default method for snippet
+        toggle_class: function (type, value, $li) {
+            var $lis = this.$el.find('[data-toggle_class]').add(this.$el).filter('[data-toggle_class]');
 
-            // add or remove html class
-            if (np.$prev) {
-                this.$target.removeClass(np.$prev.data('value') || "");
+            function map ($lis) {
+                return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
             }
-            if (np.$next) {
-                this.$target.addClass(np.$next.data('value') || "");
+            var classes = map($lis);
+            var active_classes = map($lis.filter('.active, :has(.active)'));
+
+            this.$target.removeClass(classes);
+            this.$target.addClass(active_classes);
+
+            if (type !== 'reset') {
+                this.$target.toggleClass(value);
             }
         },
+        select_class: function (type, value, $li) {
+            var $lis = this.$el.find('[data-select_class]').add(this.$el).filter('[data-select_class]');
 
-        clean_for_save: dummy
-    });
+            var classes = $lis.map(function () {return $(this).data('select_class');}).get();
 
-    website.snippet.options.background = website.snippet.Option.extend({
-        _get_bg: function () {
-            return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+            this.$target.removeClass(classes.join(" "));
+            if(value) this.$target.addClass(value);
         },
-        _set_bg: function (src) {
-            this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : "");
+        eval: function (type, value, $li) {
+            var fn = new Function("node", "type", "value", "$li", value);
+            fn.call(this, this, type, value, $li);
         },
+
+        clean_for_save: dummy
+    });
+    website.snippet.options.background = website.snippet.Option.extend({
         start: function () {
             this._super();
-            var src = this._get_bg();
-            this.$el.find("li[data-value].active.oe_custom_bg").data("src", src);
+            var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+            if (this.$target.hasClass('oe_custom_bg')) {
+                this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
+            }
         },
-        select: function(np) {
-            var self = this;
-            this._super(np);
-            if (np.$next) {
-                if (np.$next.hasClass("oe_custom_bg")) {
-                    var $image = $('<img class="hidden"/>');
-                    $image.attr("src", np.$prev ? np.$prev.data("src") : '');
-                    $image.appendTo(self.$target);
-
-                    self.element = new CKEDITOR.dom.element($image[0]);
-                    var editor = new website.editor.MediaDialog(self, self.element);
-                    editor.appendTo(document.body);
-                    editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
-
-                    $image.on('saved', self, function (o) {
-                        var src = $image.attr("src");
-                        self._set_bg(src);
-                        np.$next.data("src", src);
-                        self.$target.trigger("snippet-style-change", [self, np]);
-                        $image.remove();
-                    });
-                    editor.on('cancel', self, function () {
-                        if (!np.$prev || np.$prev.data("src") === "") {
-                            self.$target.removeClass(np.$next.data("value"));
-                            self.$target.trigger("snippet-style-change", [self, np]);
-                        }
-                        $image.remove();
-                    });
-                } else {
-                    this._set_bg(np.$next.data("src"));
-                }
+        background: function(type, value, $li) {
+            if (value && value.length) {
+                this.$target.css("background-image", 'url(' + value + ')');
+                this.$target.addClass("oe_img_bg");
             } else {
-                this._set_bg(false);
-                this.$target.removeClass(np.$prev.data("value"));
+                this.$target.css("background-image", "");
+                this.$target.removeClass("oe_img_bg").removeClass("oe_custom_bg");
             }
         },
-        preview: function (np) {
-            this._super(np);
-            if (np.$next) {
-                this._set_bg(np.$next.data("src"));
-            }
+        choose_image: function(type, value, $li) {
+            if(type !== "click") return;
+
+            var self = this;
+            var $image = $('<img class="hidden"/>');
+            $image.attr("src", value);
+            $image.appendTo(self.$target);
+
+            self.element = new CKEDITOR.dom.element($image[0]);
+            var editor = new website.editor.MediaDialog(self, self.element);
+            editor.appendTo(document.body);
+            editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
+
+            $image.on('saved', self, function (o) {
+                var value = $image.attr("src");
+                self.$target.css("background-image", 'url(' + value + ')');
+                self.$el.find('li[data-choose_image]').data("background", value).attr("data-background", value);
+                self.$target.trigger("snippet-option-change", [self]);
+                $image.remove();
+                self.$target.addClass('oe_custom_bg oe_img_bg');
+                self.set_active();
+            });
+            editor.on('cancel', self, function () {
+                self.$target.trigger("snippet-option-change", [self]);
+                $image.remove();
+            });
         },
         set_active: function () {
             var self = this;
-            var bg = self.$target.css("background-image");
-            this.$el.find('li').removeClass("active");
-            this.$el.find('li').removeClass("btn-primary");
-            var $active = this.$el.find('li[data-value]')
-                .filter(function () {
-                    var $li = $(this);
-                    return  ($li.data('src') && bg.indexOf($li.data('src')) >= 0) ||
-                            (!$li.data('src') && self.$target.hasClass($li.data('value')));
-                })
-                .first();
-            if (!$active.length) {
-                $active = this.$target.css("background-image") !== 'none' ?
-                    this.$el.find('li[data-value].oe_custom_bg') :
-                    this.$el.find('li[data-value=""]');
+            var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+            this._super();
+
+            this.$el.find('li[data-background]:not([data-background=""])')
+                .removeClass("active")
+                .each(function () {
+                    var background = $(this).data("background") || $(this).attr("data-background");
+                    if ((src.length && background.length && src.indexOf(background) !== -1) || (!src.length && !background.length)) {
+                        $(this).addClass("active");
+                    }
+                });
+
+            if (!this.$el.find('li[data-background].active').size()) {
+                this.$el.find('li[data-background=""]:not([data-choose_image])').addClass("active");
+            } else {
+                this.$el.find('li[data-background=""]:not([data-choose_image])').removeClass("active");
             }
+        }
+    });
+
+    website.snippet.options.colorpicker = website.snippet.Option.extend({
+        start: function () {
+            var self = this;
+            var res = this._super();
 
-            //don't set active on an OpenDialog link, else it not possible to click on it again after.
-            // TODO in Saas-4 - Once bootstrap is in less
-            //      - add a class active-style to get the same display but without the active behaviour used by bootstrap in JS.
-            var classStr = _.string.contains($active[0].className, "oe_custom_bg") ? "btn-primary" : "active";
-            $active.addClass(classStr);
-            this.$el.find('li:has(li[data-value].active)').addClass(classStr);
+            this.$el.find('li').append( openerp.qweb.render('website.colorpicker') );
+
+            var classes = [];
+            this.$el.find("table.colorpicker td > *").map(function () {
+                var $color = $(this);
+                var color = $color.attr("class");
+                if (self.$target.hasClass(color)) {
+                    self.color = color;
+                    $color.parent().addClass("selected");
+                }
+                classes.push(color);
+            });
+            this.classes = classes.join(" ");
+
+            this.bind_events();
+            return res;
+        },
+        bind_events: function () {
+            var self = this;
+            var $td = this.$el.find("table.colorpicker td");
+            var $colors = $td.children();
+            $colors
+                .mouseenter(function () {
+                    self.$target.removeClass(self.classes).addClass($(this).attr("class"));
+                })
+                .mouseleave(function () {
+                    self.$target.removeClass(self.classes)
+                        .addClass($td.filter(".selected").children().attr("class"));
+                })
+                .click(function () {
+                    $td.removeClass("selected");
+                    $(this).parent().addClass("selected");
+                });
         }
     });
 
         },
         clean_for_save: function () {
             this._super();
-            this.$target.find(".item").removeClass("next prev left right active");
-            this.$target.find(".item:first").addClass("active");
-            this.$indicators.find('li').removeClass('active');
-            this.$indicators.find('li:first').addClass('active');
+            this.$target.find(".item").removeClass("next prev left right active")
+                .first().addClass("active");
+            this.$indicators.find('li').removeClass('active')
+                .first().addClass("active");
         },
         start : function () {
             var self = this;
             this.id = this.$target.attr("id");
             this.$inner = this.$target.find('.carousel-inner');
             this.$indicators = this.$target.find('.carousel-indicators');
-
-            this.$el.find(".js_add").on('click', function () {self.on_add_slide(); return false;});
-            this.$el.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;});
-
             this.$target.carousel('pause');
             this.rebind_event();
         },
-        on_add_slide: function () {
+        add_slide: function (type, value) {
+            if(type !== "click") return;
+
             var self = this;
             var cycle = this.$inner.find('.item').length;
             var $active = this.$inner.find('.item.active, .item.prev, .item.next').first();
             },0);
             return $clone;
         },
-        on_remove_slide: function () {
+        remove_slide: function (type, value) {
+            if(type !== "click") return;
+
             if (this.remove_process) {
                 return;
             }
                 this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
             }
         },
+        interval : function(type, value) {
+            this.$target.attr("data-interval", value);
+        },
+        set_active: function () {
+            this.$el.find('li[data-interval]').removeClass("active")
+                .filter('li[data-interval='+this.$target.attr("data-interval")+']').addClass("active");
+        },
     });
     website.snippet.options.carousel = website.snippet.options.slider.extend({
         getSize: function () {
         },
         clean_for_save: function () {
             this._super();
-            this.$target.css("background-image", "");
-            this.$target.removeClass(this._class);
+            this.$target.removeClass('oe_img_bg ' + this._class).css("background-image", "");
         },
         load_style_options : function () {
             this._super();
-            $(".snippet-style-size li[data-value='']").remove();
+            $(".snippet-option-size li[data-value='']").remove();
         },
         start : function () {
             var self = this;
                 if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c;
                 return self._class || "";
             };
-            this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) {
-                var $active = self.$target.find(".item.active");
-                if (style['snippet-option-id'] === "size") return;
-                if (style['snippet-option-id'] === "background") {
-                    $active.css("background-image", self.$target.css("background-image"));
-                }
-                if (np.$prev) {
-                    $active.removeClass(np.$prev.data("value"));
+            this.$target.on('slid.bs.carousel', function () {
+                if(self.editor && self.editor.styles.background) {
+                    self.editor.styles.background.$target = self.$target.find(".item.active");
+                    self.editor.styles.background.set_active();
                 }
-                if (np.$next) {
-                    $active.addClass(np.$next.data("value"));
-                    add_class(np.$next.data("value"));
-                }
-            });
-            this.$target.on('slid', function () { // slide.bs.carousel
-                var $active = self.$target.find(".item.active");
-                self.$target
-                    .css("background-image", $active.css("background-image"))
-                    .removeClass(add_class($active.attr("class")))
-                    .addClass($active.attr("class"))
-                    .trigger("snippet-style-reset");
-
                 self.$target.carousel("pause");
             });
-            this.$target.trigger('slid');
+            this.$target.trigger('slid.bs.carousel');
         },
-        on_add_slide: function () {
-            var $clone = this._super();
+        add_slide: function (type, data) {
+            if(type !== "click") return;
 
+            var $clone = this._super(type, data);
             // choose an other background
             var bg = this.$target.data("snippet-option-ids").background;
             if (!bg) return $clone;
 
-            var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)");
-            var styles_index = $styles.index($styles.filter(".active")[0]);
-            $styles.removeClass("active");
-            var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]);
+            var $styles = bg.$el.find("li[data-background]");
+            var $select = $styles.filter(".active").removeClass("active").next("li[data-background]");
+            if (!$select.length) {
+                $select = $styles.first();
+            }
             $select.addClass("active");
-            $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : "");
-            $clone.addClass($select.data("value") || "");
+            $clone.css("background-image", $select.data("background") ? "url('"+ $select.data("background") +"')" : "");
 
             return $clone;
         },
             this.$target.find('.item.text_image, .item.image_text, .item.text_only').find('.container > .carousel-caption > div, .container > img.carousel-image').attr('contentEditable', 'true');
         },
     });
-
     website.snippet.options.marginAndResize = website.snippet.Option.extend({
         start: function () {
             var self = this;
             return this.grid;
         },
 
-        onFocus : function () {
+        on_focus : function () {
             this._super();
             this.change_cursor();
         },
         hide_remove_button: function() {
             this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length);
         },
-        onFocus : function () {
+        on_focus : function () {
             this._super();
             this.hide_remove_button();
         },
             return false;
         },
         on_remove: function () {
-            if (!this.$target.siblings().length) {
-                var $parent = this.$target.parents(".row:first");
-                if($parent.find("[class*='col-md']").length > 1) {
-                    return false;
-                } else {
-                    if (!$parent.data("snippet-editor")) {
-                        this.BuildingBlock.create_overlay($parent);
-                    }
-                    $parent.data("snippet-editor").on_remove();
-                }
-            }
             this._super();
             this.hide_remove_button();
-            return false;
         },
         on_resize: function (compass, beginClass, current) {
             if (compass === 'w') {
         },
     });
 
-    website.snippet.options["resize"] = website.snippet.options.marginAndResize.extend({
+    website.snippet.options.resize = website.snippet.options.marginAndResize.extend({
         getSize: function () {
             this.grid = this._super();
             this.grid.size = 8;
                 this.$target.data("snippet-view", new website.snippet.animationRegistry.parallax(this.$target));
             }
             this.scroll();
-            this.$target.on('snippet-style-change snippet-style-preview', function () {
+            this.$target.on('snippet-option-change snippet-option-preview', function () {
                 self.$target.data("snippet-view").set_values();
             });
             this.$target.attr('contentEditable', 'false');
 
             this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true');
         },
-        scroll: function () {
-            var self = this;
-            var $ul = this.$el.find('ul[name="parallax-scroll"]');
-            var $li = $ul.find("li");
-            var speed = this.$target.data('scroll-background-ratio') || 0.6 ;
-            $ul.find('[data-value="' + speed + '"]').addClass('active');
-            $li.on('click', function (event) {
-                $li.removeClass("active");
-                $(this).addClass("active");
-                var speed =  $(this).data('value');
-                self.$target.attr('data-scroll-background-ratio', speed);
-                self.$target.data("snippet-view").set_values();
-                return false;
-            });
+        scroll: function (type, value) {
+            this.$target.attr('data-scroll-background-ratio', value);
             this.$target.data("snippet-view").set_values();
         },
+        set_active: function () {
+            var value = this.$target.attr('data-scroll-background-ratio') || 0;
+            this.$el.find('[data-scroll]').removeClass("active")
+                .filter('[data-scroll="' + (this.$target.attr('data-scroll-background-ratio') || 0) + '"]').addClass("active");
+        },
         clean_for_save: function () {
             this._super();
             this.$target.find(".parallax")
         start: function () {
             var self = this;
             this._super();
-
-            this.$el.find(".clear-style").click(function (event) {
-                self.$target.removeClass("fa-spin").attr("style", "");
-                self.resetTransfo();
-            });
-
-            this.$el.find(".style").click(function (event) {
-                var settings = self.$target.data("transfo").settings;
-                self.$target.transfo({ hide: (settings.hide = !settings.hide) });
-            });
-
             this.$overlay.find('.oe_snippet_clone, .oe_handles').addClass('hidden');
-
             this.$overlay.find('[data-toggle="dropdown"]')
                 .on("mousedown", function () {
                     self.$target.transfo("hide");
                 });
         },
+        style: function (type, value) {
+            if (type !== 'click') return;
+            var settings = this.$target.data("transfo").settings;
+            this.$target.transfo({ hide: (settings.hide = !settings.hide) });
+        },
+        clear_style: function (type, value) {
+            if (type !== 'click') return;
+            this.$target.removeClass("fa-spin").attr("style", "");
+            this.resetTransfo();
+        },
         resetTransfo: function () {
             var self = this;
             this.$target.transfo("destroy");
                 })
                 .mouseover();
         },
-        onFocus : function () {
+        on_focus : function () {
             this.resetTransfo();
         },
-        onBlur : function () {
+        on_blur : function () {
             this.$target.transfo("hide");
         },
     });
             website.snippet.start_animation(true, this.$target);
 
             $(document.body).on("media-saved", self, function (event, prev , item) {
-                self.editor.onBlur();
+                self.editor.on_blur();
                 self.BuildingBlock.make_active(false);
                 if (self.$target.parent().data("oe-field") !== "image") {
                     self.BuildingBlock.make_active($(item));
                 }
             });
-
-            this.$el.find(".edition").click(function (event) {
-                event.preventDefault();
-                event.stopPropagation();
-                self.element = new CKEDITOR.dom.element(self.$target[0]);
-                new website.editor.MediaDialog(self, self.element).appendTo(document.body);
-            });
         },
-        onFocus : function () {
+        edition: function (type, value) {
+            if(type !== "click") return;
+            this.element = new CKEDITOR.dom.element(this.$target[0]);
+            new website.editor.MediaDialog(this, this.element).appendTo(document.body);
+        },
+        on_focus : function () {
             var self = this;
             if (this.$target.parent().data("oe-field") === "image") {
                 this.$overlay.addClass("hidden");
         },
     });
 
-
     website.snippet.Editor = openerp.Class.extend({
         init: function (BuildingBlock, dom) {
             this.BuildingBlock = BuildingBlock;
             this.$target = $(dom);
             this.$overlay = this.$target.data('overlay');
-            this.snippet_id = this.$target.data("snippet-id");
-            this._readXMLData();
             this.load_style_options();
             this.get_parent_block();
             this.start();
         },
 
-        /*
-        *  _readXMLData
-        *  Read data XML and set value into:
-        *  this.$el :
-        *       all xml data
-        *  this.$overlay :
-        *       Dom hover the $target who content options
-        */
-        _readXMLData: function() {
-            var self = this;
-            if(this && this.BuildingBlock && this.BuildingBlock.$snippets) {
-                this.$el = this.BuildingBlock.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
-            }
-            var $options = this.$overlay.find(".oe_overlay_options");
-            if ($options.find(".oe_options ul li").length) {
-                $options.find(".oe_options").removeClass("hidden");
-            }
-        },
-
-
         // activate drag and drop for the snippets in the snippet toolbar
         _drag_and_drop: function(){
             var self = this;
             self.BuildingBlock.activate_insertion_zones({
                 siblings: self.selector_siblings,
                 children: self.selector_children,
-                vertical_children: self.selector_vertical_children,
             });
 
             $("body").addClass('move-important');
             this.styles = {};
             this.selector_siblings = [];
             this.selector_children = [];
-            this.selector_vertical_children = [];
-            _.each(website.snippet.templateOptions, function (val) {
+            _.each(website.snippet.templateOptions, function (val, option_id) {
                 if (!self.$target.is(val.selector)) {
                     return;
                 }
-                if (val['selector-siblings']) self.selector_siblings.push(val['selector-siblings']);
-                if (val['selector-children']) self.selector_children.push(val['selector-children']);
-                if (val['selector-vertical-children']) self.selector_vertical_children.push(val['selector-vertical-children']);
-
-                var style = val['snippet-option-id'];
-                var Editor = website.snippet.options[style] || website.snippet.Option;
-                var editor = self.styles[style] = new Editor(self.BuildingBlock, self, self.$target, style);
-                $ul.append(editor.$el.addClass("snippet-style-" + style));
+                if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
+                if (val['drop-in']) self.selector_children.push(val['drop-in']);
+
+                var option = val['option'];
+                var Editor = website.snippet.options[option] || website.snippet.Option;
+                var editor = self.styles[option] = new Editor(self.BuildingBlock, self, self.$target, option_id);
+                $ul.append(editor.$el.addClass("snippet-option-" + option));
             });
             this.selector_siblings = this.selector_siblings.join(",");
             if (this.selector_siblings === "")
             this.selector_children = this.selector_children.join(",");
             if (this.selector_children === "")
                 this.selector_children = false;
-            this.selector_vertical_children = this.selector_vertical_children.join(",");
-            if (this.selector_vertical_children === "")
-                this.selector_vertical_children = false;
 
-            if (!this.selector_siblings && !this.selector_children && !this.selector_vertical_children) {
-                this.$overlay.find(".oe_snippet_move").addClass('hidden');
+            if (!this.selector_siblings && !this.selector_children) {
+                this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden');
             }
 
 
         },
 
         on_remove: function () {
-            this.onBlur();
+            this.on_blur();
             var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0));
             for (var i in this.styles){
                 this.styles[i].on_remove();
             }
             delete this.BuildingBlock.snippets[index];
+
+            // remove node and his empty
+            var parent,
+                node = this.$target.parent()[0];
+
             this.$target.remove();
+            function check(node) {
+                if ($(node).outerHeight() > 8) {
+                    return false;
+                }
+                for (var k=0; k<node.children.length; k++) {
+                    if (node.children[k].tagName || node.children[k].textContent.match(/[^\s]/)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            while (check(node)) {
+                parent = node.parentNode;
+                parent.removeChild(node);
+                node = parent;
+            }
+
             this.$overlay.remove();
             return false;
         },
             }
         },
 
-        /* onFocus
+        /* on_focus
         *  This method is called when the user click inside the snippet in the dom
         */
-        onFocus : function () {
+        on_focus : function () {
             this.$overlay.addClass('oe_active');
             for (var i in this.styles){
-                this.styles[i].onFocus();
+                this.styles[i].on_focus();
             }
         },
 
-        /* onFocus
+        /* on_focus
         *  This method is called when the user click outside the snippet in the dom, after a focus
         */
-        onBlur : function () {
+        on_blur : function () {
             for (var i in this.styles){
-                this.styles[i].onBlur();
+                this.styles[i].on_blur();
             }
             this.$overlay.removeClass('oe_active');
         },