odoo.define('web_editor.snippets.options', function (require) { 'use strict'; var Class = require('web.Class'); var ajax = require('web.ajax'); var core = require('web.core'); var base = require('web_editor.base'); var editor = require('web_editor.editor'); var widget = require('web_editor.widget'); var animation = require('web_editor.snippets.animation'); var qweb = core.qweb; /* ----- Editor option (object link the the xml with data-js) ---- */ var Option = Class.extend({ // initialisation (don't overwrite) init: function (BuildingBlock, editor, $target, option_id) { this.buildingBlock = BuildingBlock; this.editor = editor; this.$target = $target; var option = this.buildingBlock.templateOptions[option_id]; var styles = this.$target.data("snippet-option-ids") || {}; styles[option_id] = this; this.$target.data("snippet-option-ids", styles); this.$overlay = this.$target.data('overlay') || $('
'); this.option= option_id; this.$el = option.$el.find(">li").clone(); this.data = option.$el.data(); }, // helper for this.$target.find $: function (selector) { return this.$target.find(selector); }, _bind_li_menu: function () { this.$el.filter("li:hasData").find('a:first') .off('mouseenter click') .on('mouseenter click', _.bind(this._mouse, this)); this.$el .off('mouseenter click', "li:hasData a") .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this)); 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 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') { this.set_active(); this.$target.trigger("snippet-option-change", [this]); } else { this.$target.trigger("snippet-option-preview", [this]); } }, /* * select and set item active or not (add highlight item and his parents) * called before start */ set_active: function () { 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"); }, start: function () { this.set_active(); this.$target.on('snippet-option-reset', _.bind(this.set_active, this)); this._bind_li_menu(); }, on_focus : function () { this._bind_li_menu(); }, on_blur : function () { }, on_clone: function ($clone) { }, on_move: function () { }, on_remove: function () { }, drop_and_build_snippet: function () { }, reset: function () { var self = this; 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 (data-only can be used) select: function (type, $li) { var self = this, $methods = [], el = $li[0]; if ($li.data('only') && type !== $li.data('only')) { return; } if (type==="click") { this.reset(); this.buildingBlock.parent.rte.historyRecordUndo(this.$target); } function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId' && k !== 'only';} 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; } 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+"'"); } } }); }, // 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]'); function map ($lis) { return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" "); } 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]'); var classes = $lis.map(function () {return $(this).data('select_class');}).get(); this.$target.removeClass(classes.join(" ")); if(value) this.$target.addClass(value); }, eval: function (type, value, $li) { var fn = new Function("node", "type", "value", "$li", value); fn.call(this, this, type, value, $li); }, clean_for_save: editor.dummy }); /* ----- default options ---- */ // to remove after 9.0 (keep for compatibility without update with -u) var media = Option.extend({ start: function () { var self =this; this._super(); animation.start(true, this.$target); this.$target.closest(".o_editable").on("content_changed", function () { if (!self.$target.parent().length) { self.$target.remove(); self.$overlay.remove(); } }); }, edition: function (type, value) { if(type !== "click") return; var self = this; var _editor = new widget.MediaDialog(this.$target.closest('.o_editable'), this.$target[0]); _editor.appendTo(document.body); _editor.on('saved', this, function (item, old) { self.editor.on_blur(); self.buildingBlock.make_active(false); if (self.$target.parent().data("oe-field") !== "image") { setTimeout(function () { self.buildingBlock.make_active($(item)); },0); } $(item).trigger("content_changed"); }); }, on_focus : function () { var self = this; var $parent = this.$target.parent(); if ($parent.data("oe-field") === "image" && $parent.hasClass('o_editable')) { this.$overlay.addClass("hidden"); self.edition('click', null); self.buildingBlock.make_active(false); } } }); var colorpicker = Option.extend({ start: function () { var self = this; var res = this._super(); if (!this.$el.find('.colorpicker').length) { this.$el.find('li').append( qweb.render('web_editor.colorpicker') ); } if (this.$el.data('area')) { this.$target = this.$target.find(this.$el.data('area')); this.$el.removeData('area').removeAttr('area'); } var classes = []; this.$el.find(".colorpicker button").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(".colorpicker td"); var $colors = this.$el.find(".colorpicker button"); $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"); self.$target.closest(".o_editable").trigger("content_changed"); }); } }); var background = Option.extend({ start: function ($change_target) { this.$target = $change_target || this.$target; this._super(); 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); } }, background: function(type, value, $li) { if (value && value.length) { this.$target.attr("style", 'background-image: url(' + value + '); ' + (this.$target.attr("style") || '').replace(/background-image:[^;]+/, '') ); this.$target.addClass("oe_img_bg"); } else { this.$target.css("background-image", ""); this.$target.removeClass("oe_img_bg").removeClass("oe_custom_bg"); } }, select_class : function(type, value, $li) { this.background(type, '', $li); this._super(type, value, $li); }, choose_image: function(type, value, $li) { if(type !== "click") return; var self = this; var $image = $(''); $image.attr("src", value); $image.appendTo(self.$target); var _editor = new widget.MediaDialog(null, $image[0]); _editor.appendTo(document.body); _editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden'); _editor.on('saved', self, function () { var value = $image.attr("src"); $image.remove(); self.$el.find('li[data-choose_image]').data("background", value).attr("data-background", value); self.background(type, value,$li); self.$target.addClass('oe_custom_bg'); self.$target.trigger("snippet-option-change", [self]); self.set_active(); }); _editor.on('cancel', self, function () { $image.remove(); }); }, set_active: function () { var self = this; var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); this._super(); if (this.$target.hasClass('oe_custom_bg')) { this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src); } 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"); } } }); /* t-field options */ var many2one = Option.extend({ start: function () { var self = this; this.Model = this.$target.data('oe-many2one-model'); this.ID = +this.$target.data('oe-many2one-id'); // create search button and bind search bar this.$btn = $(qweb.render("web_editor.many2one.button")) .insertAfter(this.$overlay.find('.oe_options')); this.$ul = this.$btn.find("ul"); this.$search = this.$ul.find('li:first'); this.$search.find('input').on('mousedown click mouseup keyup keydown', function (e) { e.stopPropagation(); }); // move menu item setTimeout(function () { if (self.$overlay.find('.oe_options').hasClass('hidden')) { self.$btn.css('height', '0').find('> a').addClass('hidden'); self.$ul.show().css({ 'top': '-24px', 'margin': '0', 'padding': '2px 0', 'position': 'relative' }); } else { self.$btn.find('a').on('click', function (e) { self.clear(); }); } },0); // bind search input this.$search.find('input') .focus() .on('keyup', function(e) { self.find_existing($(this).val()); }); // bind result this.$ul.on('click', "li:not(:first) a", function (e) { self.select_record(this); }); }, on_focus: function () { this.$target.attr('contentEditable', 'false'); this.clear(); this._super(); }, clear: function () { var self = this; this.$search.siblings().remove(); self.$search.find('input').val(""); setTimeout(function () { self.$search.find('input').focus(); },0); }, find_existing: function (name) { var self = this; var domain = []; if (!name || !name.length) { self.$search.siblings().remove(); return; } if (isNaN(+name)) { if (this.Model !== "res.partner") { domain.push(['name', 'ilike', name]); } else { domain.push('|', ['name', 'ilike', name], ['email', 'ilike', name]); } } else { domain.push(['id', '=', name]); } ajax.jsonRpc('/web/dataset/call_kw', 'call', { model: this.Model, method: 'search_read', args: [domain, this.Model === "res.partner" ? ['name', 'display_name', 'city', 'country_id'] : ['name', 'display_name']], kwargs: { order: 'name DESC', limit: 5, context: base.get_context(), } }).then(function (result){ self.$search.siblings().remove(); self.$search.after(qweb.render("web_editor.many2one.search",{contacts:result})); }); }, get_contact_rendering: function (options) { return ajax.jsonRpc('/web/dataset/call_kw', 'call', { model: 'ir.qweb.field.contact', method: 'get_record_to_html', args: [[this.ID]], kwargs: { options: options, context: base.get_context(), } }); }, select_record: function (li) { var self = this; this.ID = +$(li).data("id"); this.$target.attr('data-oe-many2one-id', this.ID).data('oe-many2one-id', this.ID); this.buildingBlock.parent.rte.historyRecordUndo(this.$target); this.$target.trigger('content_changed'); if (self.$target.data('oe-type') === "contact") { $('[data-oe-contact-options]') .filter('[data-oe-model="'+self.$target.data('oe-model')+'"]') .filter('[data-oe-id="'+self.$target.data('oe-id')+'"]') .filter('[data-oe-field="'+self.$target.data('oe-field')+'"]') .filter('[data-oe-contact-options!="'+self.$target.data('oe-contact-options')+'"]') .add(self.$target) .attr('data-oe-many2one-id', self.ID).data('oe-many2one-id', self.ID) .each(function () { var $node = $(this); self.get_contact_rendering($node.data('oe-contact-options')) .then(function (html){ $node.html(html); }); }); } else { self.$target.html($(li).data("name")); } setTimeout(function () { self.buildingBlock.make_active(false); },0); } }); /* end*/ return { 'registry': { 'media': media, 'colorpicker': colorpicker, 'background': background, 'many2one': many2one, }, 'Class': Option }; });