Skip to content

Commit

Permalink
[IMP] web_editor: lazy load the wysiwyg
Browse files Browse the repository at this point in the history
Issue: wysiwyg asset slow down the loading of the website, error
inadvertently introduced: #29775

The assets are now loaded assynchroneously, when the editor is needed,
its assets will be loaded.

closes #30700
  • Loading branch information
Gorash committed Feb 14, 2019
1 parent 752a3a5 commit 2be0221
Show file tree
Hide file tree
Showing 31 changed files with 955 additions and 806 deletions.
7 changes: 5 additions & 2 deletions addons/mass_mailing/static/src/js/mass_mailing_widget.js
Expand Up @@ -12,6 +12,9 @@ var _t = core._t;

var MassMailingFieldHtml = FieldHtml.extend({
xmlDependencies: (FieldHtml.prototype.xmlDependencies || []).concat(["/mass_mailing/static/src/xml/mass_mailing.xml"]),
jsLibs: [
'/mass_mailing/static/src/js/mass_mailing_snippets.js',
],

custom_events: _.extend({}, FieldHtml.prototype.custom_events, {
snippets_loaded: '_onSnippetsLoaded',
Expand Down Expand Up @@ -305,7 +308,7 @@ var MassMailingFieldHtml = FieldHtml.extend({
this.$content.focusIn();
}
}
this.wysiwyg.snippets.trigger('reload_snippet_dropzones');
this.wysiwyg.trigger('reload_snippet_dropzones');
},

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -442,7 +445,7 @@ var MassMailingFieldHtml = FieldHtml.extend({
selectedTheme = themeParams;

// Notify form view
self.wysiwyg._onChange();
self.wysiwyg.getEditable().trigger('change');
$dropdown.find('.dropdown-menu').removeClass('show');
$dropdown.find('.dropdown-item.selected').removeClass('selected');
$dropdown.find('.dropdown-item:eq(' + themesParams.indexOf(selectedTheme) + ')').addClass('selected');
Expand Down
Expand Up @@ -46,7 +46,7 @@ QUnit.module('field html', {
}
if (xmlId === 'template.assets_all_style') {
return $.when({
cssLibs: $('head link[href]:not([type="image/x-icon"])').map(function () {
cssLibs: $('link[href]:not([type="image/x-icon"])').map(function () {
return $(this).attr('href');
}).get(),
cssContents: ['.field_body {background-color: red;}']
Expand Down
16 changes: 13 additions & 3 deletions addons/mass_mailing/views/editor_field_html.xml
Expand Up @@ -7,15 +7,17 @@
</template>

<template id="assets_mail_themes_edition"> <!-- maybe to remove and convert into a field dumy with attr invisible if the template is not selected -->
<t t-call="web._assets_helpers"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/webclient.scss"/>
<link rel="stylesheet" type="text/scss" href="/web_editor/static/src/scss/wysiwyg_variables.scss"/>
<t t-call="web._assets_helpers">
<link rel="stylesheet" type="text/scss" href="/web_editor/static/src/scss/wysiwyg_variables.scss"/>
</t>
<link rel="stylesheet" type="text/scss" href="/mass_mailing/static/src/scss/mass_mailing.ui.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/webclient.scss"/>
</template>

<template id="iframe_css_assets_edit">
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<t t-call-assets="web_editor.assets_wysiwyg" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes" t-js="false"/>
<t t-call-assets="mass_mailing.assets_mail_themes_edition" t-js="false"/>
</template>
Expand All @@ -26,6 +28,14 @@

<template id="qunit_suite" inherit_id="web.qunit_suite">
<xpath expr="." position="inside">
<script type="text/javascript">
odoo.define('mass_mailing.FieldHtml.test', function (require) {
'use strict';
var MassMailingFieldHtml = require('mass_mailing.FieldHtml');
MassMailingFieldHtml.include({jsLibs: []});
});
</script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_snippets.js"/>
<script type="text/javascript" src="/mass_mailing/static/tests/mass_mailing_html_tests.js"/>
</xpath>
</template>
Expand Down
1 change: 0 additions & 1 deletion addons/mass_mailing/views/mass_mailing_template.xml
Expand Up @@ -7,7 +7,6 @@

<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_widget.js"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_snippets.js"></script>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/mass_mailing/static/src/js/mass_mailing_campaign_kanban_record.js"></script>
Expand Down
37 changes: 33 additions & 4 deletions addons/web/static/src/js/core/widget.js
Expand Up @@ -84,6 +84,32 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* @type {null|string[]}
*/
xmlDependencies: null,
/**
* List of paths to css files that need to be loaded before the widget can
* be rendered. This will not induce loading anything that has already been
* loaded.
*
* @type {null|string[]}
*/
cssLibs: null,
/**
* List of paths to js files that need to be loaded before the widget can
* be rendered. This will not induce loading anything that has already been
* loaded.
*
* @type {null|string[]}
*/
jsLibs: null,
/**
* List of xmlID that need to be loaded before the widget can be rendered.
* The content css (link file or style tag) and js (file or inline) of the
* assets are loaded.
* This will not induce loading anything that has already been
* loaded.
*
* @type {null|string[]}
*/
assetLibs: null,

/**
* Constructs the widget and sets its parent if a parent is given.
Expand Down Expand Up @@ -115,13 +141,16 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, {
* @returns {Deferred}
*/
willStart: function () {
var defs = [];
if (this.xmlDependencies) {
var defs = _.map(this.xmlDependencies, function (xmlPath) {
defs.push.apply(defs, _.map(this.xmlDependencies, function (xmlPath) {
return ajax.loadXML(xmlPath, core.qweb);
});
return $.when.apply($, defs);
}));
}
return $.when();
if (this.jsLibs || this.cssLibs || this.assetLibs) {
defs.push(ajax.loadLibs(this));
}
return $.when.apply($, defs);
},
/**
* Method called after rendering. Mostly used to bind actions, perform
Expand Down
10 changes: 0 additions & 10 deletions addons/web/static/src/js/fields/abstract_field.js
Expand Up @@ -35,8 +35,6 @@ var field_utils = require('web.field_utils');
var Widget = require('web.Widget');

var AbstractField = Widget.extend({
cssLibs: [],
jsLibs: [],
events: {
'keydown': '_onKeydown',
},
Expand Down Expand Up @@ -182,14 +180,6 @@ var AbstractField = Widget.extend({
this.resetOnAnyFieldChange = true;
}
},
/**
* Loads the libraries listed in this.jsLibs and this.cssLibs
*
* @override
*/
willStart: function () {
return $.when(ajax.loadLibs(this), this._super.apply(this, arguments));
},
/**
* When a field widget is appended to the DOM, its start method is called,
* and will automatically call render. Most widgets should not override this.
Expand Down
3 changes: 2 additions & 1 deletion addons/web_editor/static/src/js/backend/convert_inline.js
@@ -1,7 +1,6 @@
odoo.define('web_editor.convertInline', function (require) {
'use strict';

var fonts = require('wysiwyg.fonts');
var FieldHtml = require('web_editor.field.html');

/**
Expand Down Expand Up @@ -189,6 +188,8 @@ function getMatchedCSSRules(a) {
* converted to images
*/
function fontToImg($editable) {
var fonts = odoo.__DEBUG__.services["wysiwyg.fonts"];

$editable.find('.fa').each(function () {
var $font = $(this);
var icon, content;
Expand Down
27 changes: 21 additions & 6 deletions addons/web_editor/static/src/js/backend/field_html.js
Expand Up @@ -4,12 +4,12 @@ odoo.define('web_editor.field.html', function (require) {
var ajax = require('web.ajax');
var basic_fields = require('web.basic_fields');
var core = require('web.core');
var Wysiwyg = require('web_editor.wysiwyg');
var Wysiwyg = require('web_editor.wysiwyg.root');
var field_registry = require('web.field_registry');

var TranslatableFieldMixin = basic_fields.TranslatableFieldMixin;

var QWeb = core.qweb;
var assetsLoaded = false;

/**
* FieldHtml Widget
Expand Down Expand Up @@ -40,8 +40,23 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
*/
willStart: function () {
this._onUpdateIframeId = 'onLoad_' + _.uniqueId('FieldHtml');
var defAsset = this.nodeOptions.cssReadonly && ajax.loadAsset(this.nodeOptions.cssReadonly);
return $.when(this._super().then(Wysiwyg.prepare.bind(Wysiwyg, this)), defAsset);
var defAsset = null;
if (this.nodeOptions.cssReadonly) {
defAsset = ajax.loadAsset(this.nodeOptions.cssReadonly);
}

if (!assetsLoaded) { // avoid flickering when begin to edit
assetsLoaded = $.Deferred();
var wysiwyg = new Wysiwyg(this, {});
wysiwyg.attachTo($('<textarea>')).then(function () {
wysiwyg.destroy();
var def = assetsLoaded;
assetsLoaded = true;
def.resolve();
});
}

return $.when(this._super(), assetsLoaded, defAsset);
},
/**
* @override
Expand Down Expand Up @@ -147,7 +162,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
// by default this is synchronous because the assets are already loaded in willStart
// but it can be async in the case of options such as iframe, snippets...
return this.wysiwyg.attachTo(this.$target).then(function () {
self.$content = self.wysiwyg.$el;
self.$content = self.wysiwyg.$editor;
self._onLoadWysiwyg();
});
},
Expand Down Expand Up @@ -444,7 +459,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
position: 'absolute',
right: '+5px',
});
var $toolbar = this.$content.closest('.note-editor').find('.note-toolbar');
var $toolbar = this.$content.find('.note-toolbar');
$toolbar.css('position', 'relative');
$toolbar.append($button);
},
Expand Down
1 change: 0 additions & 1 deletion addons/web_editor/static/src/js/common/ace.js
Expand Up @@ -191,7 +191,6 @@ var ViewEditor = Widget.extend({
willStart: function () {
return $.when(
this._super.apply(this, arguments),
ajax.loadLibs(this),
this._loadResources()
);
},
Expand Down
5 changes: 5 additions & 0 deletions addons/web_editor/static/src/js/wysiwyg/plugin/font.js
Expand Up @@ -534,6 +534,11 @@ registry.addJob(function (wysiwyg) {
if ('web_editor.colorpicker' in QWeb.templates) {
return;
}

if (wysiwyg.isDestroyed()) {
throw new Error('The Wysiwyg are destroyed before this loading');
}

var options = {};
wysiwyg.trigger_up('getRecordInfo', {
recordInfo: options,
Expand Down
62 changes: 62 additions & 0 deletions addons/web_editor/static/src/js/wysiwyg/root.js
@@ -0,0 +1,62 @@
odoo.define('web_editor.wysiwyg.root', function (require) {
'use strict';

var Widget = require('web.Widget');

var assetsLoaded = false;

var WysiwygRoot = Widget.extend({
assetLibs: ['web_editor.compiled_assets_wysiwyg'],

publicMethods: ['isDirty', 'save', 'getValue', 'setValue', 'getEditable', 'on', 'trigger'],

/**
* @see 'web_editor.wysiwyg' module
**/
init: function (parent, params) {
this._super.apply(this, arguments);
this._params = params;
this.$editor = null;
},
/**
* Load assets
*
* @override
**/
willStart: function () {
var self = this;

var $target = this.$el;
this.$el = null;

return this._super().then(function () {
if (!assetsLoaded) {
var Wysiwyg = odoo.__DEBUG__.services['web_editor.wysiwyg'];
_.each(['getRange', 'setRange', 'setRangeFromNode'], function (methodName) {
WysiwygRoot[methodName] = Wysiwyg[methodName].bind(Wysiwyg);
});
assetsLoaded = true;
}

var Wysiwyg = self._getWysiwygContructor();
var instance = new Wysiwyg(self, self._params);
self._params = null;

_.each(self.publicMethods, function (methodName) {
self[methodName] = instance[methodName].bind(instance);
});

return instance.attachTo($target).then(function () {
self.$editor = instance.$el;
});
});
},

_getWysiwygContructor: function () {
return odoo.__DEBUG__.services['web_editor.wysiwyg'];
}
});

return WysiwygRoot;

});
40 changes: 4 additions & 36 deletions addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
Expand Up @@ -19,7 +19,10 @@ var Wysiwyg = Widget.extend({
wysiwyg_blur: '_onWysiwygBlur',
},
defaultOptions: {
codeview: config.debug
codeview: config.debug,
recordInfo: {
context: {},
},
},

/**
Expand Down Expand Up @@ -693,41 +696,6 @@ var Wysiwyg = Widget.extend({
// Public helper
//--------------------------------------------------------------------------

/**
* Load wysiwyg assets if needed.
*
* @see Wysiwyg.createReadyFunction
* @param {Widget} parent
* @returns {$.Promise}
*/
Wysiwyg.prepare = (function () {
var assetsLoaded = false;
var def;
return function prepare(parent) {
if (assetsLoaded) {
return $.when();
}
if (def) {
return def;
}
def = $.Deferred();
var timeout = setTimeout(function () {
throw _t("Can't load assets of the wysiwyg editor");
}, 10000);
var wysiwyg = new Wysiwyg(parent, {
recordInfo: {
context: {},
}
});
wysiwyg.attachTo($('<textarea>')).then(function () {
assetsLoaded = true;
clearTimeout(timeout);
wysiwyg.destroy();
def.resolve();
});
return def;
};
})();
/**
* @param {Node} node (editable or node inside)
* @returns {Object}
Expand Down

0 comments on commit 2be0221

Please sign in to comment.