CKEditor не отображается с динамически генерируемым полем формы WTForms

Использование WTForms, Flask, CKEditor

У меня есть поле в форме с динамическим количеством записей; их можно как добавлять, так и удалять. Настроить это в WTForms было непросто, и я воспользовался решением, обсуждаемым в по этой ссылке. Я не очень хорошо знаком с JS, поэтому мне сложно адаптировать эту функцию для создания CKEditorField вместо обычного StringField/<textarea>.

Приведенный ниже код работает, ничего не «сломано», но RTE не отображается, только обычное текстовое поле. Это заставило меня поверить, что проблема в HTML, а не в файлах Flask, но на всякий случай я включил оба.

То, что я пробовал до сих пор:

(1) Маркер StringField в форме ввода заменен на CKEditorField (Forms.py, ниже).

(2) Добавление «class=ckeditor» в возвращаемую текстовую область и ручной вызов функций замены CKEditor (хотя в документации говорится, что это включается автоматически) (app.js)

(3) Создание и возврат вызовов CKEditor.config в HTML для каждого вновь сгенерированного поля (в документации предлагается использовать это для каждого CKE на странице и в других местах, где я использую CKE ((нединамически создаваемые)) работаем с этим на месте) (app.js)

(4) Замена возврата <textarea> вызовом CKEditor.create (я думаю, что именно здесь и кроется ответ, но он не сработал, и я не могу понять, что еще попробовать) (app.js/ веб-страница.html)

Также отметим, что другое поле формы, планы, успешно работает с редактором.

Формы.py

class Entry(FlaskForm):
    # (1)
    # bullet = StringField(validators=[validators.DataRequired()])
    bullet = CKEditorField(validators=[validators.DataRequired(), validators.Length(max=1000)])

class MyForm(FlaskForm):
    entries = FieldList(FormField(Entry), min_entries=0)
    plans = CKEditorField('Plans', [validators.InputRequired(), validators.Length(max=1000)])

приложение.js

$(document).ready(function() {   
    // (2) 
    // These are redundant but not working
    CKEDITOR.replaceClass = 'ckeditor';
    CKEDITOR.replaceAll( 'ckeditor' ); 


    var addCount = 1;            
    $("#addNewField").click(function() {
        var newInput = $("#entries");
        newInput.append(GetDynamicTextBox("", addCount));
        $("#entries").append(newInput);
        addCount += 1;
        listCKE();        
    });

    // (3)
    // CKE Documentation says to include a config instruction
    // (in the form of) {{ ckeditor.config(name = "misc_risks") }}
    // This fn is my attempt at returning one of those for each added entry to the HTML
    // This doesn't work, just returns raw text printed on the page
    function listCKE() {
        var items = ``;
        for (var i=0; i<addCount; i++) {
            items += `{{ckeditor.config(name = "subdir`+i.toString()+`")}}`;
        }
        console.info(items)
        document.getElementById("listcke").innerHTML = items;
    }
        
});


function GetDynamicTextBox(value, addCount) {
    return '<div>' + 'Entry&nbsp' + addCount + ':&nbsp' +
    // (4)
    // Tried replacing <textarea> with ckeditor create func; didn't work
    // '{{ckeditor.create(name = "subdir' + addCount + '")}}' +
    // (2)
    '<textarea class = "ckeditor" name = "subdir' + addCount + '"type = "text" value = "' + value + '"> </textarea>&nbsp;' +
    '<input type = "button" value = "Remove" class = "remove" />' + '</div>' ;
}

$(function () {
    $("#addNewField").click(function() {
        $("#entries").append(GetDynamicTextBox("", addCount));                
            
    });
    
    $("body").on("click", ".remove", function () {
        $(this).closest("div").remove();
        addCount -= 1;
    });
}); 

веб-страница.html

<form method = "POST" enctype = "multipart/form-data">
    {{form.csrf_token}}

            <button type = "button" id = "addNewField">Add Entry</button>
            {% for subdir in form.subdirs %}
            {{ forms.render_field(subdir.name) }}
            {% endfor %}
            {{ form.entries() }}

            {{ form.plans.label }} {{ form.plans() }}

    <p><input type = "submit" value = "Submit"></p>
</form>
{{ ckeditor.load(pkg_type = "basic") }}
{{ ckeditor.config(name = "plans") }}
<!-- (4) This was used with the listCKE function in app.js. Didn't work, only returned raw text in {{}} to page -->
<!-- <div id = "listcke"></div> -->
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Механизм шаблонов Flask jinja работает на стороне сервера, а javascript — на клиенте. По этой причине смешивание выражений из обоих миров, как вы пытаетесь сделать во время выполнения кода JavaScript, невозможно.

В следующем примере показан один из способов динамического добавления полей, использующих CKEditor. Здесь для каждого поля устанавливаются уникальные атрибуты name и id, а для связанной метки устанавливается соответствующий атрибут for.

from flask import (
    Flask, 
    render_template, 
    request, 
)
from flask_ckeditor import (
    CKEditor, 
    CKEditorField
)
from flask_wtf import FlaskForm
from wtforms import FieldList, FormField

app = Flask(__name__)
app.secret_key = 'your secret here'

ckeditor = CKEditor(app)

class EntryForm(FlaskForm):
    class Meta:
        csrf = False

    bullet = CKEditorField('Entry')

class MyForm(FlaskForm):
    entries = FieldList(FormField(EntryForm), min_entries=0)

@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm(request.form)
    if form.validate_on_submit():
        print(form.entries.data)
    return render_template('index.html', **locals())
<!DOCTYPE html>
<html>
<head>
    <meta charset = "utf-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1">
    <title>Index</title>
</head>
<body>
    <form method = "post">
        {{ form.csrf_token }}

        <div>
            <button type = "button" id = "btn-add">Add</button>
        </div>

        <div id = "entries">
            {% for f in form.entries %}
            <div>
                {{ f.bullet.label() }}
                {{ f.bullet() }}
                <button type = "button" class = "btn-remove">Remove</button>
            </div>
            {% endfor -%}
        </div>

        <div>
            <button type = "submit">Submit</button>
        </div>

    </form>

    {{ ckeditor.load() }}
    {# Create a configuration for each CKEditor that already exists. #}
    {% for f in form if f.type == CKEditorField -%}
        {{ ckeditor.config(name=f.name) }}
    {% endfor -%}
    <script 
        src = "https://code.jquery.com/jquery-3.7.1.slim.min.js" 
        integrity = "sha256-kmHvs0B+OpCW5GVHUNjv9rOmY0IvSIRcf7zGUDTDQM8 = " 
        crossorigin = "anonymous"></script>
    <script>

        $(document).ready(() => {

            const entriesEl = $('#entries');

            $('#btn-add').click(() => {
                // If the button to add is clicked...

                let ids = ['entries-0-bullet'];

                const sel = 'textarea[name$ = "-bullet"]';
                const entries = entriesEl.find(sel);
                if (entries.length) {
                    // ... and there are already input fields ...
                    const lastEntry = entries.last().closest('div');
                    ids = $.map($(lastEntry).children(sel), function(elem) {
                        // ... extract the name attribute of the last input field 
                        // and generate a new unique id from it.
                        const attr = $(elem).attr('name'), 
                            s = attr.replace(/(\w+)-(\d+)-bullet$/, (match, p1, p2) => {
                                return `${p1}-${parseInt(p2)+1}-bullet`;
                            });
                        return s;
                    });
                } 

                // For each id created a block with the new input field. 
                // Register a function to remove the block and configure the CKEditor.
                $.each(ids, function(index, value) {
                    const newEntry = $.parseHTML(`<div>
                        <label for = "${value}">Entry</label>
                        <textarea class = "ckeditor" id = "${value}" name = "${value}"></textarea>
                        <button type = "button" class = "btn-remove">Remove</button>
                    </div>`);
                    $(newEntry).children('.btn-remove').click(function() {
                        $(this).closest('div').remove();
                    })
                    entriesEl.append(newEntry);
                    CKEDITOR.replace(value);
                });

            });

            // Register a function to remove fields that already exist.
            $('.btn-remove').click(function() {
                $(this).closest('div').remove();
            });

        });
        
    </script>
</body>
</html>

Другие вопросы по теме