4 /* Building block / Snippet Editor
6 The building blocks appear in the edit bar website. These prebuilt html block
7 allowing the designer to easily generate content on a page (drag and drop).
8 Options allow snippets to add customizations part html code according to their
9 selector (jQuery) and javascript object.
11 How to create content?
13 Designers can add their own html block in the "snippets" (/website/views/snippets.xml).
14 The block must be added in one of four menus (structure, content, feature or effect).
17 <div class="oe_snippet_thumbnail">
18 <img class="oe_snippet_thumbnail_img" src="...image src..."/>
19 <span class="oe_snippet_thumbnail_title">...Block Name...</span>
21 <div class="oe_snippet_body">
24 The block with class 'oe_snippet_body' is inserted in the page.
25 This class is removed when the block is dropped.
26 The block can be made of any html tag and content. -->
30 How to create options?
32 Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml).
35 <div data-snippet-option-id='...' <!-- Required: javascript object id (but javascript
36 for this option object is not required) -->
37 data-selector="..." <!-- Required: jQuery selector.
38 Apply options on all The part of html who
39 match with this jQuery selector.
40 E.g.: If the selector is div, all div will be selected
41 and can be highlighted and assigned an editor. -->
42 data-selector-siblings="..." <!-- Optional: jQuery selector.
43 The html part can be insert or move beside
44 the selected html block -->
45 data-selector-children="..." <!-- Optional: jQuery selector.
46 The html part can be insert or move inside
47 the selected html block -->
48 data-selector-vertical-children='...'> <!-- Optional: jQuery selector.
49 The html part can be insert or move inside
50 the selected html block. The drop zone is
51 displayed vertically -->
53 <li><a href="#">...</a></li> <!-- Optional: html li list.
54 List of menu items displayed in customize
55 menu. If the li tag have 'data-class', the
56 class is automaticcally added or removed to
57 the html content when the user select this item. -->
59 <li class="dropdown-submenu" <!-- Optional: html li list exemple. !-->
60 data-required="true"> <!-- Optional: if only one item can be selected
61 and can't be unselect. !-->
62 <a tabindex="-1" href="#">...</a> <!-- bootstrap dropdown button !-->
63 <ul class="dropdown-menu">
64 <li data-value="text_only"><a>...</a></li> <!-- by default data-value is apply
65 like a class to html block !-->
70 How to create a javascript object for an options?
72 openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({
73 // start is called when the user click into a block or when the user drop a block
74 // into the page (just after the init method).
75 // start is usually used to bind event.
77 // this.$target: block html inserted inside the page
78 // this.$el: html li list of this options
79 // this.$overlay: html editor overlay who content resize bar, customize menu...
80 start: function () {},
83 // onFocus is called when the user click inside the block inserted in page
84 // and when the user drop on block into the page
85 onFocus : function () {},
88 // onBlur is called when the user click outside the block inserted in page, if
89 // the block is focused
90 onBlur : function () {},
93 // on_clone is called when the snippet is duplicate
94 // @variables: $clone is allready inserted is the page
95 on_clone: function ($clone) {},
98 // on_remove is called when the snippet is removed (dom is removing after this tigger)
99 on_remove: function () {},
102 // drop_and_build_snippet is called just after that a thumbnail is drag and dropped
103 // into a drop zone. The content is already inserted in the page.
104 drop_and_build_snippet: function () {},
106 // select is called when a user select an item in the li list of options
107 // By default, if the li item have a data-value attribute, the data-vlue it's apply
108 // like a class to the html block (this.$target)
109 // @variables: next_previous = {$next, $prev}
110 // $next = next item selected or false
111 // $prev = previous item selected or false
112 select: function (event, next_previous) {}
114 // preview is called when a user is on mouse over or mouse out of an item
115 // variables: next_previous = {$next, $prev}
116 // $next = next item selected or false
117 // $prev = previous item selected or false
118 preview: function (event, next_previous) {}
121 // clean_for_save is called just before to save the vue
122 // Sometime it's important to remove or add some datas (contentEditable, added
123 // classes to a running animation...)
124 clean_for_save: function () {}
128 // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped
129 // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated
133 var dummy = function () {};
135 var website = openerp.website;
136 website.add_template_file('/website/static/src/xml/website.snippets.xml');
138 website.EditorBar.include({
141 $("[data-oe-model]").on('click', function (event) {
142 var $this = $(event.srcElement);
143 var tag = $this[0] && $this[0].tagName.toLowerCase();
144 if (!(tag === 'a' || tag === "button") && !$this.parents("a, button").length) {
145 self.$('[data-action="edit"]').parent().effect('bounce', {distance: 18, times: 5}, 250);
148 return this._super();
152 $("[data-oe-model] *, [data-oe-type=html] *").off('click');
153 window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
154 this.snippets.appendTo(this.$el);
155 website.snippet.stop_animation();
156 this.on('rte:ready', this, function () {
157 self.snippets.$button.removeClass("hidden");
158 website.snippet.start_animation();
159 $("#wrapwrap *").off('mousedown mouseup click');
162 return this._super.apply(this, arguments);
165 this.snippets.clean_for_save();
170 /* ----- SNIPPET SELECTOR ---- */
172 var observer = new website.Observer(function (mutations) {
173 if (!_(mutations).find(function (m) {
174 return m.type === 'childList' && m.addedNodes.length > 0;
180 if (!website.snippet) website.snippet = {};
181 website.snippet.templateOptions = {};
182 website.snippet.globalSelector = "";
183 website.snippet.selector = [];
184 website.snippet.BuildingBlock = openerp.Widget.extend({
185 template: 'website.snippets',
187 init: function (parent) {
188 this.parent = parent;
189 this._super.apply(this, arguments);
190 if(!$('#oe_manipulators').length){
191 $("<div id='oe_manipulators'></div>").appendTo('body');
193 this.$active_snipped_id = false;
196 observer.observe(document.body, {
201 dom_filter: function (dom, sibling) {
202 if (typeof dom === "string") {
203 var include = "[data-oe-model]";
204 var sdom = dom.split(',');
206 _.each(sdom, function (val) {
207 val = val.replace(/^\s+|\s+$/g, '');
208 dom += include + " " + val + ", ";
210 val = val.split(" ");
211 dom += val.shift() + include + val.join(" ") + ", ";
214 dom = dom.replace(/,\s*$/g, '');
217 return (!sibling && $(dom).is("[data-oe-model]")) || $(dom).parents("[data-oe-model]").length ? $(dom) : $("");
223 this.$button = $(openerp.qweb.render('website.snippets_button'))
224 .prependTo(this.parent.$("#website-top-edit ul"))
227 this.$button.click(function () {
228 self.make_active(false);
229 self.$el.toggleClass("hidden");
231 $("#wrapwrap").click(function () {
232 self.$el.addClass("hidden");
235 this.fetch_snippet_templates();
237 this.bind_snippet_click_editor();
239 this.$el.addClass("hidden");
241 $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
245 this.getParent().on('change:height', this, function (editor) {
246 self.$el.css('top', editor.get('height'));
248 self.$el.css('top', this.parent.get('height'));
250 _get_snippet_url: function () {
251 return '/website/snippets';
253 fetch_snippet_templates: function () {
256 openerp.jsonRpc(this._get_snippet_url(), 'call', {})
257 .then(function (html) {
261 var $styles = $html.find("[data-snippet-option-id]");
262 $styles.each(function () {
263 var $style = $(this);
264 var style_id = $style.data('snippet-option-id');
265 website.snippet.templateOptions[style_id] = {
266 'snippet-option-id' : style_id,
267 'selector': $style.data('selector'),
269 'selector-siblings': $style.data('selector-siblings'),
270 'selector-children': $style.data('selector-children'),
271 'selector-vertical-children': $style.data('selector-vertical-children'),
272 'data': $style.data()
274 selector.push($style.data('selector'));
276 $styles.addClass("hidden");
277 website.snippet.globalSelector = selector.join(",");
279 self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet");
280 self.$el.append($html);
284 self.$snippets.each(function () {
285 if (self.snippet_have_dropzone($(this)))
288 if (!snippets) self.$button.css("display", "none");
290 self.make_snippet_draggable(self.$snippets);
293 cover_target: function ($el, $target){
294 var pos = $target.offset();
295 var mt = parseInt($target.css("margin-top") || 0);
296 var mb = parseInt($target.css("margin-bottom") || 0);
298 'width': $target.outerWidth(),
299 'top': pos.top - mt - 5,
302 $el.find(".oe_handle.e,.oe_handle.w").css({'height': $target.outerHeight() + mt + mb+1});
303 $el.find(".oe_handle.s").css({'top': $target.outerHeight() + mt + mb});
304 $el.find(".oe_handle.size").css({'top': $target.outerHeight() + mt});
305 $el.find(".oe_handle.s,.oe_handle.n").css({'width': $target.outerWidth()-2});
308 this.$el.removeClass("hidden");
311 this.$el.addClass("hidden");
314 snippet_have_dropzone: function ($snippet) {
315 return (($snippet.data('selector-siblings') && this.dom_filter($snippet.data('selector-siblings')).size() > 0) ||
316 ($snippet.data('selector-children') && this.dom_filter($snippet.data('selector-children')).size() > 0) ||
317 ($snippet.data('selector-vertical-children') && this.dom_filter($snippet.data('selector-vertical-children')).size() > 0));
320 bind_snippet_click_editor: function () {
322 var snipped_event_flag;
323 $("#wrapwrap").on('click', function (event) {
324 var srcElement = event.srcElement || (event.originalEvent && (event.originalEvent.originalTarget || event.originalEvent.target));
325 if (snipped_event_flag || !srcElement) {
328 snipped_event_flag = true;
330 setTimeout(function () {snipped_event_flag = false;}, 0);
331 var $target = $(srcElement);
333 if ($target.parents(".oe_overlay").length) {
337 if (!$target.is(website.snippet.globalSelector)) {
338 $target = $target.parents(website.snippet.globalSelector).first();
341 if (!self.dom_filter($target).length) {
344 if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
347 self.make_active($target);
350 snippet_blur: function ($snippet) {
352 if ($snippet.data("snippet-editor")) {
353 $snippet.data("snippet-editor").onBlur();
357 snippet_focus: function ($snippet) {
359 if ($snippet.data("snippet-editor")) {
360 $snippet.data("snippet-editor").onFocus();
364 clean_for_save: function () {
366 var options = website.snippet.options;
367 var template = website.snippet.templateOptions;
368 for (var k in options) {
369 if (template[k] && options[k].prototype.clean_for_save !== dummy) {
370 var $snippet = this.dom_filter(template[k].selector);
371 $snippet.each(function () {
372 new options[k](self, null, $(this), k).clean_for_save();
376 $("*[contentEditable], *[attributeEditable]")
377 .removeAttr('contentEditable')
378 .removeAttr('attributeEditable');
380 make_active: function ($snippet) {
381 if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) {
384 if (this.$active_snipped_id) {
385 this.snippet_blur(this.$active_snipped_id);
386 this.$active_snipped_id = false;
388 if ($snippet && $snippet.length) {
389 if(_.indexOf(this.snippets, $snippet.get(0)) === -1) {
390 this.snippets.push($snippet.get(0));
392 this.$active_snipped_id = $snippet;
393 this.create_overlay(this.$active_snipped_id);
394 this.snippet_focus($snippet);
396 $("#oe_snippets").trigger('snippet-activated', $snippet);
398 $snippet.trigger('snippet-activated', $snippet);
401 create_overlay: function ($snippet) {
402 if (typeof $snippet.data("snippet-editor") === 'undefined') {
403 var $targets = this.activate_overlay_zones($snippet);
404 if (!$targets.length) return;
405 $snippet.data("snippet-editor", new website.snippet.Editor(this, $snippet));
407 this.cover_target($snippet.data('overlay'), $snippet);
410 path_eval: function(path){
412 path = path.split('.');
414 obj = obj[path.shift()];
415 }while(path.length && obj);
419 // activate drag and drop for the snippets in the snippet toolbar
420 make_snippet_draggable: function($snippets){
422 var $tumb = $snippets.find(".oe_snippet_thumbnail:first");
423 var left = $tumb.outerWidth()/2;
424 var top = $tumb.outerHeight()/2;
425 var $toInsert, dropped, $snippet, action, snipped_id;
427 $snippets.draggable({
433 handle: ".oe_snippet_thumbnail",
441 // snippet_selectors => to get selector-siblings, selector-children, selector-vertical-children
443 $toInsert = $snippet.find('.oe_snippet_body').clone();
446 var selector_siblings = [];
447 var selector_children = [];
448 var selector_vertical_children = [];
449 for (var k in website.snippet.templateOptions) {
450 if ($toInsert.is(website.snippet.templateOptions[k].selector)) {
451 selector.push(website.snippet.templateOptions[k].selector);
452 if (website.snippet.templateOptions[k]['selector-siblings'])
453 selector_siblings.push(website.snippet.templateOptions[k]['selector-siblings']);
454 if (website.snippet.templateOptions[k]['selector-children'])
455 selector_children.push(website.snippet.templateOptions[k]['selector-children']);
456 if (website.snippet.templateOptions[k]['selector-vertical-children'])
457 selector_vertical_children.push(website.snippet.templateOptions[k]['selector-vertical-children']);
461 action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate';
463 if( action === 'insert'){
464 if (!selector_siblings.length && !selector_children.length && !selector_vertical_children.length) {
465 console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+
466 "data-selector-siblings, data-selector-children or data-selector-vertical-children tag for mutate action");
469 self.activate_insertion_zones({
470 siblings: selector_siblings.join(","),
471 children: selector_children.join(","),
472 vertical_children: selector_vertical_children.join(","),
475 } else if( action === 'mutate' ){
476 if (!$snippet.data('selector')) {
477 console.debug($snippet.data("snippet-id") + " have not oe_snippet_body class and have not data-selector tag");
480 var $targets = self.activate_overlay_zones(selector_children.join(","));
481 $targets.each(function(){
482 var $clone = $(this).data('overlay').clone();
483 $clone.addClass("oe_drop_zone").data('target', $(this));
484 $(this).data('overlay').after($clone);
489 $('.oe_drop_zone').droppable({
491 if( action === 'insert'){
493 $(this).first().after($toInsert);
497 var prev = $toInsert.prev();
498 if( action === 'insert' && this === prev[0]){
505 stop: function(ev, ui){
506 $toInsert.removeClass('oe_snippet_body');
508 if (action === 'insert' && ! dropped && $('.oe_drop_zone') && ui.position.top > 3) {
509 var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
516 $('.oe_drop_zone').droppable('destroy').remove();
522 setTimeout(function () {
523 $("#oe_snippets").trigger('snippet-dropped', $target);
525 website.snippet.start_animation(true, $target);
526 // drop_and_build_snippet
527 self.create_overlay($target);
528 if ($target.data("snippet-editor")) {
529 $target.data("snippet-editor").drop_and_build_snippet();
531 for (var k in website.snippet.templateOptions) {
532 $target.find(website.snippet.templateOptions[k].selector).each(function () {
533 var $snippet = $(this);
534 self.create_overlay($snippet);
535 if ($snippet.data("snippet-editor")) {
536 $snippet.data("snippet-editor").drop_and_build_snippet();
541 // reset snippet for rte
542 $target.removeData("snippet-editor");
543 if ($target.data("overlay")) {
544 $target.data("overlay").remove();
545 $target.removeData("overlay");
547 $target.find(website.snippet.globalSelector).each(function () {
548 var $snippet = $(this);
549 $snippet.removeData("snippet-editor");
550 if ($snippet.data("overlay")) {
551 $snippet.data("overlay").remove();
552 $snippet.removeData("overlay");
555 self.create_overlay($target);
558 self.make_active($target);
567 // return the original snippet in the editor bar from a snippet id (string)
568 get_snippet_from_id: function(id){
569 return $('.oe_snippet').filter(function(){
570 return $(this).data('snippet-id') === id;
574 // Create element insertion drop zones. two css selectors can be provided
575 // selector.children -> will insert drop zones as direct child of the selected elements
576 // in case the selected elements have children themselves, dropzones will be interleaved
578 // selector.siblings -> will insert drop zones after and before selected elements
579 activate_insertion_zones: function(selector){
581 var child_selector = selector.children;
582 var sibling_selector = selector.siblings;
583 var vertical_child_selector = selector.vertical_children;
585 var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
588 self.dom_filter(child_selector).each(function (){
590 $zone.find('> *:not(.oe_drop_zone):visible').after(zone_template);
591 $zone.prepend(zone_template);
595 if(vertical_child_selector){
596 self.dom_filter(vertical_child_selector).each(function (){
598 var $template = $(zone_template).addClass("oe_vertical");
600 var $lastinsert = false;
603 $zone.find('> *:not(.oe_drop_zone):visible').each(function () {
605 $template.css('height', ($col.outerHeight() + parseInt($col.css("margin-top")) + parseInt($col.css("margin-bottom")))+'px');
606 $lastinsert = $template.clone();
607 $(this).after($lastinsert);
609 temp_left = $col.position().left;
610 if (left === temp_left) {
611 $col.prev(".oe_drop_zone.oe_vertical").remove();
612 $col.before($template.clone().css("clear", "left"));
615 $col.before($template.clone());
621 $zone.prepend($template.css('height', $zone.outerHeight()+'px'));
626 if(sibling_selector){
627 self.dom_filter(sibling_selector, true).each(function (){
629 if($zone.prev('.oe_drop_zone:visible').length === 0){
630 $zone.before(zone_template);
632 if($zone.next('.oe_drop_zone:visible').length === 0){
633 $zone.after(zone_template);
641 // var $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones
642 // count += $zones.length;
645 $zones = $('.oe_drop_zone > .oe_drop_zone:not(.oe_vertical)').remove(); // no recursive zones
646 count += $zones.length;
650 // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones.
651 var $zones = $('.oe_drop_zone:not(.oe_vertical)');
652 $zones.each(function (){
654 var prev = zone.prev();
655 var next = zone.next();
656 var float_prev = prev.css('float') || 'none';
657 var float_next = next.css('float') || 'none';
658 var disp_prev = prev.css('display') || null;
659 var disp_next = next.css('display') || null;
660 if( (float_prev === 'left' || float_prev === 'right')
661 && (float_next === 'left' || float_next === 'right') ){
663 }else if( !( disp_prev === null
664 || disp_next === null
665 || disp_prev === 'block'
666 || disp_next === 'block' )){
672 // generate drop zones covering the elements selected by the selector
673 // we generate overlay drop zones only to get an idea of where the snippet are, the drop
674 activate_overlay_zones: function(selector){
675 var $targets = this.dom_filter(selector);
678 if (typeof selector !== 'string' && !$targets.length) {
679 console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute.");
680 console.debug( "Wrong node(s): ", selector);
683 function is_visible($el){
684 return $el.css('display') != 'none'
685 && $el.css('opacity') != '0'
686 && $el.css('visibility') != 'hidden';
689 // filter out invisible elements
690 $targets = $targets.filter(function(){ return is_visible($(this)); });
692 // filter out elements with invisible parents
693 $targets = $targets.filter(function(){
694 var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
695 return parents.length === 0;
698 $targets.each(function () {
699 var $target = $(this);
700 if (!$target.data('overlay')) {
701 var $zone = $(openerp.qweb.render('website.snippet_overlay'));
703 // fix for pointer-events: none with ie9
704 if (document.body && document.body.addEventListener) {
705 $zone.on("click mousedown mousedown", function passThrough(event) {
706 event.preventDefault();
707 $target.each(function() {
708 // check if clicked point (taken from event) is inside element
709 event.srcElement = this;
710 $(this).trigger(event.type);
716 $zone.appendTo('#oe_manipulators');
717 $zone.data('target',$target);
718 $target.data('overlay',$zone);
720 $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () {
721 self.cover_target($zone, $target);
723 $('body').on("resize", function () {
724 self.cover_target($zone, $target);
727 self.cover_target($target.data('overlay'), $target);
734 website.snippet.options = {};
735 website.snippet.Option = openerp.Class.extend({
736 // initialisation (don't overwrite)
737 init: function (BuildingBlock, editor, $target, snippet_id) {
738 this.BuildingBlock = BuildingBlock;
739 this.editor = editor;
740 this.$target = $target;
741 var styles = this.$target.data("snippet-option-ids") || {};
742 styles[snippet_id] = this;
743 this.$target.data("snippet-option-ids", styles);
744 this.$overlay = this.$target.data('overlay') || $('<div>');
745 this['snippet-option-id'] = snippet_id;
746 var $option = website.snippet.templateOptions[snippet_id].$el;
747 this.$el = $option.find(">li").clone();
748 this.data = $option.data();
750 this.required = this.$el.data("required");
753 this.$el.find('li[data-value] a').on('mouseenter mouseleave click', _.bind(this._mouse, this));
754 this.$el.not(':not([data-value])').find("a").on('mouseenter mouseleave click', _.bind(this._mouse, this));
755 this.$target.on('snippet-style-reset', _.bind(this.set_active, this));
759 _mouse: function (event) {
762 if (event.type === 'mouseleave') {
763 if (!this.over) return;
765 } else if (event.type === 'click') {
772 if (event.type === 'mouseleave') {
773 $prev = $(event.currentTarget).parent();
774 $next = this.$el.find("li[data-value].active");
776 $prev = this.$el.find("li[data-value].active");
777 $next = $(event.currentTarget).parent();
782 if ($prev && $prev[0] === $next[0]) {
789 var np = {'$next': $next, '$prev': $prev};
791 if (event.type === 'click') {
792 setTimeout(function () {
794 self.$target.trigger("snippet-style-change", [self, np]);
796 this.select({'$next': $next, '$prev': $prev});
798 setTimeout(function () {
799 self.$target.trigger("snippet-style-preview", [self, np]);
805 * select and set item active or not (add highlight item and his parents)
806 * called before start
808 set_active: function () {
810 this.$el.find('li').removeClass("active");
811 var $active = this.$el.find('li[data-value]')
812 .filter(function () {
814 return ($li.data('value') && self.$target.hasClass($li.data('value')));
818 this.$el.find('li:has(li[data-value].active)').addClass("active");
824 onFocus : function () {
827 onBlur : function () {
830 on_clone: function ($clone) {
833 on_remove: function () {
836 drop_and_build_snippet: function () {
839 select: function (np) {
841 // add or remove html class
842 if (np.$prev && this.required) {
843 this.$target.removeClass(np.$prev.data('value' || ""));
846 this.$target.addClass(np.$next.data('value') || "");
850 preview: function (np) {
853 // add or remove html class
855 this.$target.removeClass(np.$prev.data('value') || "");
858 this.$target.addClass(np.$next.data('value') || "");
862 clean_for_save: dummy
865 website.snippet.options.background = website.snippet.Option.extend({
866 _get_bg: function () {
867 return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
869 _set_bg: function (src) {
870 this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : "");
874 var src = this._get_bg();
875 this.$el.find("li[data-value].active.oe_custom_bg").data("src", src);
877 select: function(np) {
881 if (np.$next.hasClass("oe_custom_bg")) {
882 var $image = $('<img class="hidden"/>');
883 $image.attr("src", np.$prev ? np.$prev.data("src") : '');
884 $image.appendTo(self.$target);
886 self.element = new CKEDITOR.dom.element($image[0]);
887 var editor = new website.editor.MediaDialog(self, self.element);
888 editor.appendTo(document.body);
889 editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
891 $image.on('saved', self, function (o) {
892 var src = $image.attr("src");
894 np.$next.data("src", src);
895 self.$target.trigger("snippet-style-change", [self, np]);
898 editor.on('cancel', self, function () {
899 if (!np.$prev || np.$prev.data("src") === "") {
900 self.$target.removeClass(np.$next.data("value"));
901 self.$target.trigger("snippet-style-change", [self, np]);
906 this._set_bg(np.$next.data("src"));
910 this.$target.removeClass(np.$prev.data("value"));
913 preview: function (np) {
916 this._set_bg(np.$next.data("src"));
919 set_active: function () {
921 var bg = self.$target.css("background-image");
922 this.$el.find('li').removeClass("active");
923 this.$el.find('li').removeClass("btn-primary");
924 var $active = this.$el.find('li[data-value]')
925 .filter(function () {
927 return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) ||
928 (!$li.data('src') && self.$target.hasClass($li.data('value')));
931 if (!$active.length) {
932 $active = this.$target.css("background-image") !== 'none' ?
933 this.$el.find('li[data-value].oe_custom_bg') :
934 this.$el.find('li[data-value=""]');
937 //don't set active on an OpenDialog link, else it not possible to click on it again after.
938 // TODO in Saas-4 - Once bootstrap is in less
939 // - add a class active-style to get the same display but without the active behaviour used by bootstrap in JS.
940 var classStr = _.string.contains($active[0].className, "oe_custom_bg") ? "btn-primary" : "active";
941 $active.addClass(classStr);
942 this.$el.find('li:has(li[data-value].active)').addClass(classStr);
946 website.snippet.options.slider = website.snippet.Option.extend({
947 unique_id: function () {
949 $(".carousel").each(function () {
950 var cid = 1 + parseInt($(this).attr("id").replace(/[^0123456789]/g, ''),10);
951 if (id < cid) id = cid;
953 return "myCarousel" + id;
955 drop_and_build_snippet: function() {
956 this.id = this.unique_id();
957 this.$target.attr("id", this.id);
958 this.$target.find("[data-slide]").attr("data-cke-saved-href", "#" + this.id);
959 this.$target.find("[data-target]").attr("data-target", "#" + this.id);
962 on_clone: function ($clone) {
963 var id = this.unique_id();
964 $clone.attr("id", id);
965 $clone.find("[data-slide]").attr("href", "#" + id);
966 $clone.find("[data-slide-to]").attr("data-target", "#" + id);
968 // rebind event to active carousel on edit mode
969 rebind_event: function () {
971 this.$target.find('.carousel-indicators [data-slide-to]').off('click').on('click', function () {
972 self.$target.carousel(+$(this).data('slide-to')); });
974 this.$target.attr('contentEditable', 'false');
975 this.$target.find('.oe_structure, .content>.row, [data-slide]').attr('contentEditable', 'true');
977 clean_for_save: function () {
979 this.$target.find(".item").removeClass("next prev left right active");
980 this.$target.find(".item:first").addClass("active");
981 this.$indicators.find('li').removeClass('active');
982 this.$indicators.find('li:first').addClass('active');
984 start : function () {
987 this.$target.carousel({interval: false});
988 this.id = this.$target.attr("id");
989 this.$inner = this.$target.find('.carousel-inner');
990 this.$indicators = this.$target.find('.carousel-indicators');
992 this.$el.find(".js_add").on('click', function () {self.on_add_slide(); return false;});
993 this.$el.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;});
995 this.$target.carousel('pause');
998 on_add_slide: function () {
1000 var cycle = this.$inner.find('.item').length;
1001 var $active = this.$inner.find('.item.active, .item.prev, .item.next').first();
1002 var index = $active.index();
1003 this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden");
1004 this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
1006 // clone the best candidate from template to use new features
1007 var $snippets = this.BuildingBlock.$snippets.find('.oe_snippet_body.carousel');
1010 var className = _.compact(this.$target.attr("class").split(" "));
1011 $snippets.each(function () {
1012 var len = _.intersection(_.compact(this.className.split(" ")), className).length;
1018 var $clone = $(selection).find('.item:first').clone();
1021 $clone.removeClass('active').insertAfter($active);
1022 setTimeout(function() {
1023 self.$target.carousel().carousel(++index);
1024 self.rebind_event();
1028 on_remove_slide: function () {
1029 if (this.remove_process) {
1034 var cycle = this.$inner.find('.item').length - 1;
1035 var index = this.$inner.find('.item.active').index();
1038 this.remove_process = true;
1039 var $el = this.$inner.find('.item.active');
1040 self.$target.on('slid.bs.carousel', function (event) {
1042 self.$indicators.find("li:last").remove();
1043 self.$target.off('slid.bs.carousel');
1044 self.rebind_event();
1045 self.remove_process = false;
1047 self.on_remove_slide(event);
1050 setTimeout(function () {
1051 self.$target.carousel( index > 0 ? --index : cycle );
1054 this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden");
1058 website.snippet.options.carousel = website.snippet.options.slider.extend({
1059 getSize: function () {
1060 this.grid = this._super();
1064 clean_for_save: function () {
1066 this.$target.css("background-image", "");
1067 this.$target.removeClass(this._class);
1069 load_style_options : function () {
1071 $(".snippet-style-size li[data-value='']").remove();
1073 start : function () {
1077 // set background and prepare to clean for save
1078 var add_class = function (c){
1079 if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c;
1080 return self._class || "";
1082 this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) {
1083 var $active = self.$target.find(".item.active");
1084 if (style['snippet-option-id'] === "size") return;
1085 if (style['snippet-option-id'] === "background") {
1086 $active.css("background-image", self.$target.css("background-image"));
1089 $active.removeClass(np.$prev.data("value"));
1092 $active.addClass(np.$next.data("value"));
1093 add_class(np.$next.data("value"));
1096 this.$target.on('slid', function () { // slide.bs.carousel
1097 var $active = self.$target.find(".item.active");
1099 .css("background-image", $active.css("background-image"))
1100 .removeClass(add_class($active.attr("class")))
1101 .addClass($active.attr("class"))
1102 .trigger("snippet-style-reset");
1104 self.$target.carousel("pause");
1106 this.$target.trigger('slid');
1108 on_add_slide: function () {
1109 var $clone = this._super();
1111 // choose an other background
1112 var bg = this.$target.data("snippet-option-ids").background;
1113 if (!bg) return $clone;
1115 var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)");
1116 var styles_index = $styles.index($styles.filter(".active")[0]);
1117 $styles.removeClass("active");
1118 var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]);
1119 $select.addClass("active");
1120 $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : "");
1121 $clone.addClass($select.data("value") || "");
1125 // rebind event to active carousel on edit mode
1126 rebind_event: function () {
1128 this.$target.find('.carousel-control').off('click').on('click', function () {
1129 self.$target.carousel( $(this).data('slide')); });
1132 /* Fix: backward compatibility saas-3 */
1133 this.$target.find('.item.text_image .container').find('> .carousel-caption > div, > img.carousel-image').attr('contentEditable', 'true');
1137 website.snippet.options.marginAndResize = website.snippet.Option.extend({
1138 start: function () {
1142 var resize_values = this.getSize();
1143 if (resize_values.n) this.$overlay.find(".oe_handle.n").removeClass("readonly");
1144 if (resize_values.s) this.$overlay.find(".oe_handle.s").removeClass("readonly");
1145 if (resize_values.e) this.$overlay.find(".oe_handle.e").removeClass("readonly");
1146 if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly");
1147 if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly");
1149 this.$overlay.find(".oe_handle:not(.size), .oe_handle.size .size").on('mousedown', function (event){
1150 event.preventDefault();
1152 var $handle = $(this);
1154 var resize_values = self.getSize();
1155 var compass = false;
1157 if ($handle.hasClass('n')) {
1161 else if ($handle.hasClass('s')) {
1165 else if ($handle.hasClass('e')) {
1169 else if ($handle.hasClass('w')) {
1173 else if ($handle.hasClass('size')) {
1178 var resize = resize_values[compass];
1179 if (!resize) return;
1182 if (compass === 'size') {
1183 var offset = self.$target.offset().top;
1184 if (self.$target.css("background").match(/rgba\(0, 0, 0, 0\)/)) {
1185 self.$target.addClass("resize_editor_busy");
1188 var xy = event['page'+XY];
1189 var current = resize[2] || 0;
1190 _.each(resize[0], function (val, key) {
1191 if (self.$target.hasClass(val)) {
1195 var begin = current;
1196 var beginClass = self.$target.attr("class");
1197 var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g');
1200 self.BuildingBlock.editor_busy = true;
1202 var cursor = $handle.css("cursor")+'-important';
1203 var $body = $(document.body);
1204 $body.addClass(cursor);
1206 var body_mousemove = function (event){
1207 event.preventDefault();
1208 if (compass === 'size') {
1209 var dy = event.pageY-offset;
1210 dy = dy - dy%resize;
1211 if (dy <= 0) dy = resize;
1212 self.$target.css("height", dy+"px");
1213 self.$target.css("overflow", "hidden");
1214 self.on_resize(compass, null, dy);
1215 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1218 var dd = event['page'+XY] - xy + resize[1][begin];
1219 var next = current+1 === resize[1].length ? current : (current+1);
1220 var prev = current ? (current-1) : 0;
1223 if (dd > (2*resize[1][next] + resize[1][current])/3) {
1224 self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
1225 self.$target.addClass(resize[0][next]);
1229 if (prev != current && dd < (2*resize[1][prev] + resize[1][current])/3) {
1230 self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
1231 self.$target.addClass(resize[0][prev]);
1237 self.on_resize(compass, beginClass, current);
1238 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1242 var body_mouseup = function(){
1243 $body.unbind('mousemove', body_mousemove);
1244 $body.unbind('mouseup', body_mouseup);
1245 $body.removeClass(cursor);
1246 self.BuildingBlock.editor_busy = false;
1247 self.$target.removeClass("resize_editor_busy");
1249 $body.mousemove(body_mousemove);
1250 $body.mouseup(body_mouseup);
1252 this.$overlay.find(".oe_handle.size .auto_size").on('click', function (event){
1253 self.$target.css("height", "");
1254 self.$target.css("overflow", "");
1255 self.BuildingBlock.cover_target(self.$overlay, self.$target);
1259 getSize: function () {
1264 onFocus : function () {
1266 this.change_cursor();
1269 change_cursor : function () {
1270 var _class = this.$target.attr("class") || "";
1272 var col = _class.match(/col-md-([0-9-]+)/i);
1273 col = col ? +col[1] : 0;
1275 var offset = _class.match(/col-md-offset-([0-9-]+)/i);
1276 offset = offset ? +offset[1] : 0;
1278 var overlay_class = this.$overlay.attr("class").replace(/(^|\s+)block-[^\s]*/gi, '');
1279 if (col+offset >= 12) overlay_class+= " block-e-right";
1280 if (col === 1) overlay_class+= " block-w-right block-e-left";
1281 if (offset === 0) overlay_class+= " block-w-left";
1283 var mb = _class.match(/mb([0-9-]+)/i);
1284 mb = mb ? +mb[1] : 0;
1285 if (mb >= 128) overlay_class+= " block-s-bottom";
1286 else if (!mb) overlay_class+= " block-s-top";
1288 var mt = _class.match(/mt([0-9-]+)/i);
1289 mt = mt ? +mt[1] : 0;
1290 if (mt >= 128) overlay_class+= " block-n-top";
1291 else if (!mt) overlay_class+= " block-n-bottom";
1293 this.$overlay.attr("class", overlay_class);
1297 * called when the box is resizing and the class change, before the cover_target
1298 * @compass: resize direction : 'n', 's', 'e', 'w'
1299 * @beginClass: attributes class at the begin
1300 * @current: curent increment in this.grid
1302 on_resize: function (compass, beginClass, current) {
1303 this.change_cursor();
1306 website.snippet.options["margin-y"] = website.snippet.options.marginAndResize.extend({
1307 getSize: function () {
1308 this.grid = this._super();
1309 var grid = [0,4,8,16,32,48,64,92,128];
1311 // list of class (Array), grid (Array), default value (INT)
1312 n: [_.map(grid, function (v) {return 'mt'+v;}), grid],
1313 s: [_.map(grid, function (v) {return 'mb'+v;}), grid],
1314 // INT if the user can resize the snippet (resizing per INT px)
1320 website.snippet.options["margin-x"] = website.snippet.options.marginAndResize.extend({
1321 getSize: function () {
1322 this.grid = this._super();
1323 var width = this.$target.parents(".row:first").first().outerWidth();
1325 var grid = [1,2,3,4,5,6,7,8,9,10,11,12];
1326 this.grid.e = [_.map(grid, function (v) {return 'col-md-'+v;}), _.map(grid, function (v) {return width/12*v;})];
1328 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];
1329 this.grid.w = [_.map(grid, function (v) {return 'col-md-offset-'+v;}), _.map(grid, function (v) {return width/12*v;}), 12];
1333 _drag_and_drop_after_insert_dropzone: function(){
1335 var $zones = $(".row:has(> .oe_drop_zone)").each(function () {
1337 var width = $row.innerWidth();
1339 while (width > pos + self.size.width) {
1340 var $last = $row.find("> .oe_drop_zone:last");
1341 $last.each(function () {
1342 pos = $(this).position().left;
1344 if (width > pos + self.size.width) {
1345 $row.append("<div class='col-md-1 oe_drop_to_remove'/>");
1346 var $add_drop = $last.clone();
1347 $row.append($add_drop);
1348 self._drag_and_drop_active_drop_zone($add_drop);
1353 _drag_and_drop_start: function () {
1355 this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''));
1357 _drag_and_drop_stop: function () {
1358 this.$target.addClass("col-md-offset-" + this.$target.prevAll(".oe_drop_to_remove").length);
1361 hide_remove_button: function() {
1362 this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length);
1364 onFocus : function () {
1366 this.hide_remove_button();
1368 on_clone: function ($clone) {
1369 var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, '');
1370 $clone.attr("class", _class);
1371 this.hide_remove_button();
1374 on_remove: function () {
1375 if (!this.$target.siblings().length) {
1376 var $parent = this.$target.parents(".row:first");
1377 if($parent.find("[class*='col-md']").length > 1) {
1380 if (!$parent.data("snippet-editor")) {
1381 this.BuildingBlock.create_overlay($parent);
1383 $parent.data("snippet-editor").on_remove();
1387 this.hide_remove_button();
1390 on_resize: function (compass, beginClass, current) {
1391 if (compass === 'w') {
1392 // don't change the right border position when we change the offset (replace col size)
1393 var beginCol = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0);
1394 var beginOffset = Number(beginClass.match(/col-md-offset-([0-9-]+)|$/)[1] || beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0);
1395 var offset = Number(this.grid.w[0][current].match(/col-md-offset-([0-9-]+)|$/)[1] || 0);
1399 var colSize = beginCol - (offset - beginOffset);
1402 offset = beginOffset + beginCol - 1;
1404 this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-|col-md-)([0-9-]+)/g, ''));
1406 this.$target.addClass('col-md-' + (colSize > 12 ? 12 : colSize));
1408 this.$target.addClass('col-md-offset-' + offset);
1411 this._super(compass, beginClass, current);
1415 website.snippet.options["resize"] = website.snippet.options.marginAndResize.extend({
1416 getSize: function () {
1417 this.grid = this._super();
1423 website.snippet.options.parallax = website.snippet.Option.extend({
1424 getSize: function () {
1425 this.grid = this._super();
1429 on_resize: function (compass, beginClass, current) {
1430 this.$target.data("snippet-view").set_values();
1432 start : function () {
1435 if (!self.$target.data("snippet-view")) {
1436 this.$target.data("snippet-view", new website.snippet.animationRegistry.parallax(this.$target));
1439 this.$target.on('snippet-style-change snippet-style-preview', function () {
1440 self.$target.data("snippet-view").set_values();
1442 this.$target.attr('contentEditable', 'false');
1444 this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); // saas-3 retro-compatibility
1446 this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true');
1448 scroll: function () {
1450 var $ul = this.$el.find('ul[name="parallax-scroll"]');
1451 var $li = $ul.find("li");
1452 var speed = this.$target.data('scroll-background-ratio') || 0.6 ;
1453 $ul.find('[data-value="' + speed + '"]').addClass('active');
1454 $li.on('click', function (event) {
1455 $li.removeClass("active");
1456 $(this).addClass("active");
1457 var speed = $(this).data('value');
1458 self.$target.attr('data-scroll-background-ratio', speed);
1459 self.$target.data("snippet-view").set_values();
1462 this.$target.data("snippet-view").set_values();
1464 clean_for_save: function () {
1466 this.$target.find(".parallax")
1467 .css("background-position", '')
1468 .removeAttr("data-scroll-background-offset");
1472 website.snippet.options.transform = website.snippet.Option.extend({
1473 start: function () {
1477 this.$el.find(".clear-style").click(function (event) {
1478 self.$target.removeClass("fa-spin").attr("style", "");
1479 self.resetTransfo();
1482 this.$el.find(".style").click(function (event) {
1483 var settings = self.$target.data("transfo").settings;
1484 self.$target.transfo({ hide: (settings.hide = !settings.hide) });
1487 this.$overlay.find('.oe_snippet_clone, .oe_handles').addClass('hidden');
1489 this.$overlay.find('[data-toggle="dropdown"]')
1490 .on("mousedown", function () {
1491 self.$target.transfo("hide");
1494 resetTransfo: function () {
1496 this.$target.transfo("destroy");
1497 this.$target.transfo({
1499 callback: function () {
1500 var pos = $(this).data("transfo").$center.offset();
1504 'position': 'absolute',
1506 self.$overlay.find(".oe_overlay_options").attr("style", "width:0; left:0!important; top:0;");
1507 self.$overlay.find(".oe_overlay_options > .btn-group").attr("style", "width:160px; left:-80px;");
1509 this.$target.data('transfo').$markup
1510 .on("mouseover", function () {
1511 self.$target.trigger("mouseover");
1515 onFocus : function () {
1516 this.resetTransfo();
1518 onBlur : function () {
1519 this.$target.transfo("hide");
1523 website.snippet.options.media = website.snippet.Option.extend({
1524 start: function () {
1528 website.snippet.start_animation(true, this.$target);
1530 $(document.body).on("media-saved", self, function (event, prev , item) {
1531 self.editor.onBlur();
1532 self.BuildingBlock.make_active(false);
1533 if (self.$target.parent().data("oe-field") !== "image") {
1534 self.BuildingBlock.make_active($(item));
1538 this.$el.find(".edition").click(function (event) {
1539 event.preventDefault();
1540 event.stopPropagation();
1541 self.element = new CKEDITOR.dom.element(self.$target[0]);
1542 new website.editor.MediaDialog(self, self.element).appendTo(document.body);
1545 onFocus : function () {
1547 if (this.$target.parent().data("oe-field") === "image") {
1548 this.$overlay.addClass("hidden");
1549 self.element = new CKEDITOR.dom.element(self.$target[0]);
1550 new website.editor.MediaDialog(self, self.element).appendTo(document.body);
1551 self.BuildingBlock.make_active(false);
1553 setTimeout(function () {
1554 self.$target.find(".css_editable_mode_display").removeAttr("_moz_abspos");
1560 website.snippet.Editor = openerp.Class.extend({
1561 init: function (BuildingBlock, dom) {
1562 this.BuildingBlock = BuildingBlock;
1563 this.$target = $(dom);
1564 this.$overlay = this.$target.data('overlay');
1565 this.snippet_id = this.$target.data("snippet-id");
1566 this._readXMLData();
1567 this.load_style_options();
1568 this.get_parent_block();
1574 * Read data XML and set value into:
1578 * Dom hover the $target who content options
1580 _readXMLData: function() {
1582 if(this && this.BuildingBlock && this.BuildingBlock.$snippets) {
1583 this.$el = this.BuildingBlock.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
1585 var $options = this.$overlay.find(".oe_overlay_options");
1586 if ($options.find(".oe_options ul li").length) {
1587 $options.find(".oe_options").removeClass("hidden");
1592 // activate drag and drop for the snippets in the snippet toolbar
1593 _drag_and_drop: function(){
1595 this.dropped = false;
1596 this.$overlay.draggable({
1600 handle: ".oe_snippet_move",
1605 helper: function() {
1606 var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0});
1607 $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove();
1608 $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0});
1609 $clone.appendTo("body").removeClass("hidden");
1612 start: _.bind(self._drag_and_drop_start, self),
1613 stop: _.bind(self._drag_and_drop_stop, self)
1616 _drag_and_drop_after_insert_dropzone: function (){},
1617 _drag_and_drop_active_drop_zone: function ($zones){
1621 $(".oe_drop_zone.hide").removeClass("hide");
1622 $(this).addClass("hide").first().after(self.$target);
1623 self.dropped = true;
1626 $(this).removeClass("hide");
1627 self.$target.detach();
1628 self.dropped = false;
1632 _drag_and_drop_start: function (){
1634 self.BuildingBlock.hide();
1635 self.BuildingBlock.editor_busy = true;
1637 width: self.$target.width(),
1638 height: self.$target.height()
1640 self.$target.after("<div class='oe_drop_clone' style='display: none;'/>");
1641 self.$target.detach();
1642 self.$overlay.addClass("hidden");
1644 self.BuildingBlock.activate_insertion_zones({
1645 siblings: self.selector_siblings,
1646 children: self.selector_children,
1647 vertical_children: self.selector_vertical_children,
1650 $("body").addClass('move-important');
1652 self._drag_and_drop_after_insert_dropzone();
1653 self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
1655 _drag_and_drop_stop: function (){
1657 if (!self.dropped) {
1658 $(".oe_drop_clone").after(self.$target);
1660 self.$overlay.removeClass("hidden");
1661 $("body").removeClass('move-important');
1662 $('.oe_drop_zone').droppable('destroy').remove();
1663 $(".oe_drop_clone, .oe_drop_to_remove").remove();
1664 self.BuildingBlock.editor_busy = false;
1665 self.get_parent_block();
1666 setTimeout(function () {self.BuildingBlock.create_overlay(self.$target);},0);
1669 load_style_options: function () {
1671 var $styles = this.$overlay.find('.oe_options');
1672 var $ul = $styles.find('ul:first');
1674 this.selector_siblings = [];
1675 this.selector_children = [];
1676 this.selector_vertical_children = [];
1677 _.each(website.snippet.templateOptions, function (val) {
1678 if (!self.$target.is(val.selector)) {
1681 if (val['selector-siblings']) self.selector_siblings.push(val['selector-siblings']);
1682 if (val['selector-children']) self.selector_children.push(val['selector-children']);
1683 if (val['selector-vertical-children']) self.selector_vertical_children.push(val['selector-vertical-children']);
1685 var style = val['snippet-option-id'];
1686 var Editor = website.snippet.options[style] || website.snippet.Option;
1687 var editor = self.styles[style] = new Editor(self.BuildingBlock, self, self.$target, style);
1688 $ul.append(editor.$el.addClass("snippet-style-" + style));
1690 this.selector_siblings = this.selector_siblings.join(",");
1691 if (this.selector_siblings === "")
1692 this.selector_siblings = false;
1693 this.selector_children = this.selector_children.join(",");
1694 if (this.selector_children === "")
1695 this.selector_children = false;
1696 this.selector_vertical_children = this.selector_vertical_children.join(",");
1697 if (this.selector_vertical_children === "")
1698 this.selector_vertical_children = false;
1700 if (!this.selector_siblings && !this.selector_children && !this.selector_vertical_children) {
1701 this.$overlay.find(".oe_snippet_move").addClass('hidden');
1705 if ($ul.find("li").length) {
1706 $styles.removeClass("hidden");
1708 this.$overlay.find('[data-toggle="dropdown"]').dropdown();
1711 get_parent_block: function () {
1713 var $button = this.$overlay.find('.oe_snippet_parent');
1714 var $parent = this.$target.parents(website.snippet.globalSelector).first();
1715 if ($parent.length) {
1716 $button.removeClass("hidden");
1717 $button.off("click").on('click', function (event) {
1718 event.preventDefault();
1719 setTimeout(function () {
1720 self.BuildingBlock.make_active($parent);
1724 $button.addClass("hidden");
1730 * This method is called after init and _readXMLData
1732 start: function () {
1734 this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this));
1735 this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this));
1736 this._drag_and_drop();
1739 on_clone: function () {
1740 var $clone = this.$target.clone(false);
1741 this.$target.after($clone);
1742 for (var i in this.styles){
1743 this.styles[i].on_clone($clone);
1748 on_remove: function () {
1750 var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0));
1751 for (var i in this.styles){
1752 this.styles[i].on_remove();
1754 delete this.BuildingBlock.snippets[index];
1755 this.$target.remove();
1756 this.$overlay.remove();
1761 * drop_and_build_snippet
1762 * This method is called just after that a thumbnail is drag and dropped into a drop zone
1763 * (after the insertion of this.$body, if this.$body exists)
1765 drop_and_build_snippet: function () {
1766 for (var i in this.styles){
1767 this.styles[i].drop_and_build_snippet();
1772 * This method is called when the user click inside the snippet in the dom
1774 onFocus : function () {
1775 this.$overlay.addClass('oe_active');
1776 for (var i in this.styles){
1777 this.styles[i].onFocus();
1782 * This method is called when the user click outside the snippet in the dom, after a focus
1784 onBlur : function () {
1785 for (var i in this.styles){
1786 this.styles[i].onBlur();
1788 this.$overlay.removeClass('oe_active');