Search code examples
xmlodoowysiwyg

How to add WYSIWYG on portal website odoo13


I want to add WYSIWYG HTML to the Odoo 13 website portal by using the following this tutorial, but the WYSIWYG display that I have tried only displays a loading screen so that it cannot be inputted with text. is there anything missing?

[This is displays a loading screen][2]<br><br>

XML code:

<template id="portal_my_details_sl_elrn" inherit_id="portal.portal_my_details">
    <xpath expr="//form/div/div/div/div[3]" position="before">
        <div t-attf-class="form-group #{error.get('about_me') and 'o_has_error' or ''} col-xs-12">
            <!-- <label class="col-form-label" for="about_me">About Me</label> -->
            <label class="col-form-label" for="about_me">About Me</label>
            <textarea name="about_me" id="about_me" class="form-control o_wysiwyg_loader">
                <!-- <t t-esc="about_me"/> -->
                <input name="about_me" t-attf-class="form-control #{error.get('about_me') and 'is-invalid' or ''}"
                       t-att-value="about_me or partner.about_me"/>
            </textarea>
        </div>
    </xpath>
</template>

Solution

  • In addition to the code you posted, you can add website_profile to your module depends and add o_wprofile_editor_form class to the form.

    <xpath expr="//form" position="attributes">
        <attribute name="class">o_wprofile_editor_form</attribute>
    </xpath>
    

    Or you can add the javascript website profile editor code to website.assets_frontend instead of installing website_profile.

    <template id="assets_frontend" inherit_id="website.assets_frontend">
        <xpath expr="script[last()]" position="after">
            <script type="text/javascript" src="/module_name/static/src/js/custom_editor.js"></script>
        </xpath>
    </template>
    

    The following code can be found in website_profile static folder.

    var publicWidget = require('web.public.widget');
    var wysiwygLoader = require('web_editor.loader');
    
    
    publicWidget.registry.websiteProfileEditor = publicWidget.Widget.extend({
        selector: '.o_wprofile_editor_form',
        read_events: {
            'click .o_forum_profile_pic_edit': '_onEditProfilePicClick',
            'change .o_forum_file_upload': '_onFileUploadChange',
            'click .o_forum_profile_pic_clear': '_onProfilePicClearClick',
            'click .o_wprofile_submit_btn': '_onSubmitClick',
        },
    
        /**
         * @override
         */
        start: function () {
            var def = this._super.apply(this, arguments);
            if (this.editableMode) {
                return def;
            }
    
            var $textarea = this.$('textarea.o_wysiwyg_loader');
            var loadProm = wysiwygLoader.load(this, $textarea[0], {
                recordInfo: {
                    context: this._getContext(),
                    res_model: 'res.users',
                    res_id: parseInt(this.$('input[name=user_id]').val()),
                },
            }).then(wysiwyg => {
                this._wysiwyg = wysiwyg;
            });
    
            return Promise.all([def, loadProm]);
        },
    
        //--------------------------------------------------------------------------
        // Handlers
        //--------------------------------------------------------------------------
    
        /**
         * @private
         * @param {Event} ev
         */
        _onEditProfilePicClick: function (ev) {
            ev.preventDefault();
            $(ev.currentTarget).closest('form').find('.o_forum_file_upload').trigger('click');
        },
        /**
         * @private
         * @param {Event} ev
         */
        _onFileUploadChange: function (ev) {
            if (!ev.currentTarget.files.length) {
                return;
            }
            var $form = $(ev.currentTarget).closest('form');
            var reader = new window.FileReader();
            reader.readAsDataURL(ev.currentTarget.files[0]);
            reader.onload = function (ev) {
                $form.find('.o_forum_avatar_img').attr('src', ev.target.result);
            };
            $form.find('#forum_clear_image').remove();
        },
        /**
         * @private
         * @param {Event} ev
         */
        _onProfilePicClearClick: function (ev) {
            var $form = $(ev.currentTarget).closest('form');
            $form.find('.o_forum_avatar_img').attr('src', '/web/static/src/img/placeholder.png');
            $form.append($('<input/>', {
                name: 'clear_image',
                id: 'forum_clear_image',
                type: 'hidden',
            }));
        },
        /**
         * @private
         */
        _onSubmitClick: function () {
            if (this._wysiwyg) {
                this._wysiwyg.save();
            }
        },
    });
    

    Edit:
    We need to call _onSubmitClick on form submit, add o_wprofile_submit_btn class to the submit button:

    <xpath expr="//button[@type='submit']" position="attributes">
        <attribute name="class">btn btn-primary o_wprofile_submit_btn</attribute>
    </xpath>
    

    Edit: unknown field 'files'

    The widget adds an input names files which is passed to the controller and when you click on Confirm button, details_form_validate method is called to check if keys present in data (post) are also in MANDATORY_BILLING_FIELDS or in OPTIONAL_BILLING_FIELDS.

    I suppose you are not using files field ( it is not declared in BILLING_FIELDS), to avoid the warning try to bypass verification:

    from odoo.addons.portal.controllers.portal import CustomerPortal
    
    class CustomerPortalNew(CustomerPortal):
    
        def details_form_validate(self, data):
            files = data.pop('files', None)
            res = super(CustomerPortalNew, self).details_form_validate(data)
            data['files'] = files
            return res
    

    Fix Code:
    res_partner.py

    from odoo import api, models, fields, _
    
    class ResPartner(models.Model):
        _inherit = 'res.partner'
    
        about_me = fields.Html('About Me')
    

    portal.py

    from odoo.http import Controller
    from odoo.addons.portal.controllers.portal import CustomerPortal
    
    CustomerPortal.OPTIONAL_BILLING_FIELDS.append('about_me')
    
    class CustomerPortalNew(CustomerPortal):
    
        def details_form_validate(self, data):
            files = data.pop('files', None)
            res = super(CustomerPortalNew, self).details_form_validate(data)
            data['files'] = files
            return res
    

    portal_templates.xml

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
        <template id="assets_frontend" inherit_id="website.assets_frontend">
            <xpath expr="script[last()]" position="after">
                <script type="text/javascript" src="/sl_elrn/static/src/js/website_profile.js"></script>
            </xpath>
        </template>
        <template id="portal_my_details_sl_elrn" inherit_id="portal.portal_my_details">
            <xpath expr="//form" position="attributes">
                <attribute name="class">o_wprofile_editor_form</attribute>
            </xpath>
            <xpath expr="//form/div/div/div/div[3]" position="before">
                <div t-attf-class="form-group #{error.get('about_me') and 'o_has_error' or ''} col-xl-12">
                    <label class="col-form-label" for="about_me">About Me</label>
                    <textarea name="about_me" id="about_me" style="min-height: 120px" class="form-control o_wysiwyg_loader">
                        <t t-esc="about_me or partner.about_me"/>
                    </textarea>
                </div>
            </xpath>
            <xpath expr="//button[@type='submit']" position="attributes">
                <attribute name="class">btn btn-primary o_wprofile_submit_btn</attribute>
            </xpath>
        </template>
    </odoo>