使用CKEditor5、WTForms、Flask。 --我知道这个问题看起来像是重复的,但请听我说完!
如果您认为我应该删除并重新发布作为对我原来问题的评论,请告诉我。
背景故事
这是我之前的问题这里的后续。 CKEditor 4(使用flask-ckeditor库)已经不再支持,所以我更新了我的项目以使用CKEditor 5。同样的问题再次出现,我的JavaScript代码动态添加“
Entry
”字段创建时不会渲染 CKE5 字段。
非常感谢前面的回答者,但是对JavaScript很不熟悉,尤其是像这样的小众案例。我怀疑下面显示的
ClassicEditor.create
和/或 document.querySelector
仅在首次加载网页时运行,因此会错过该时间之后创建的任何内容,即使附加了适当的类也是如此。即使我的怀疑是正确的,我也不知道从哪里开始采用更有效的怀疑,并且希望得到有关此事的任何指导。
设置
我的表单中有一个字段 (
entries
),该字段具有动态数量的条目;它们可以被添加和删除。我之前使用给出的答案(在上面的链接中)来创建 CKEditor 字段而不是纯 TextArea 字段。成功将表单字段 TextArea 的其余部分迁移到 CKE5 后,动态创建的 TextArea 再次显示为纯 TextArea 而不是 CKEditor。使用的 CKE5 实现改编自其网站上的“CDN 快速启动安装”文档。
下面的代码有效,没有任何东西“损坏”,我没有收到控制台或 Flask 错误,但 RTE 没有显示——只有一个常规文本区域。
我尝试修改之前的CKE5解决方案
我尝试将类属性“ckeditor5”添加到所有动态添加的字段(在app.js中)和
bullets
(在webpage.html中)。这似乎还不够。正如我提到的,我怀疑基于此类替换文本区域的函数不会在加载初始网页后检查动态添加的标签。
网页.html
<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/43.0.0/ckeditor5.css" />
.
.
.
<form method="POST" enctype="multipart/form-data">
{{form.csrf_token}}
<div id="entries">
{% for f in form.entries%}
<div>
{{ f.bullet.label() }}
{{ f.bullet(class_="ckeditor5") }}
<button type="button">Remove</button>
</div>
{% endfor -%}
</div>
<!-- example of another form field whose CKE5 field is working w/ this implementation -->
<p>{{ form.plans.label }} <br> {{ form.plans(class_="ckeditor5") }}</p>
<p><input type="submit" value="Submit"></p>
</form>
.
.
.
<!-- CKE5 JavaScript from documentation on their website -->
<script type="importmap">
{
"imports": {
"ckeditor5": "https://cdn.ckeditor.com/ckeditor5/43.0.0/ckeditor5.js",
"ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/43.0.0/"
}
}
</script>
<script type="module">
import {
ClassicEditor,
AccessibilityHelp,
Autosave,
Bold,
Code,
Essentials,
Heading,
Indent,
IndentBlock,
Italic,
List,
ListProperties,
Markdown,
Paragraph,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Subscript,
Superscript,
Underline,
Undo
} from 'ckeditor5';
const elements = document.querySelectorAll(".ckeditor5");
if (elements.length > 0) {
for (let i = 0; i < elements.length; i++) {
ClassicEditor
.create( elements[i] , {
plugins: [ AccessibilityHelp,
Autosave,
Bold,
Code,
Essentials,
Heading,
Indent,
IndentBlock,
Italic,
List,
ListProperties,
Markdown,
Paragraph,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Subscript,
Superscript,
Underline,
Undo
],
toolbar: {
items: [
'undo',
'redo',
'|',
'selectAll',
'|',
'heading',
'|',
'bold',
'italic',
'underline',
'strikethrough',
'subscript',
'superscript',
'code',
'removeFormat',
'|',
'specialCharacters',
'|',
'bulletedList',
'numberedList',
'outdent',
'indent',
'|',
'accessibilityHelp'
], shouldNotGroupWhenFull: false
}
} )
.then( /* ... */ )
.catch( /* ... */ );
}
}
</script>
app.js
$(document).ready(function() {
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="ckeditor5" id="${value}" name="${value}"></textarea>
<button type="button">Remove</button>
</div>`);
$(newEntry).children('.btn-remove').click(function() {
$(this).closest('div').remove();
})
entriesEl.append(newEntry);
});
});
// Register a function to remove fields that already exist.
$('.btn-remove').click(function() {
$(this).closest('div').remove();
});
});
forms.py
class Entry(FlaskForm):
class Meta:
csrf=False
bullet = TextAreaField(validators=[validators.DataRequired(), validators.Length(max=1000)])
class MyForm(FlaskForm):
plans = TextAreaField('Plans')
entries= FieldList(FormField(Entry), min_entries=0)
您的代码缺少为新创建的“Entry”中的文本区域创建编辑器的调用。这意味着显示正常的文本区域。
以下代码向您展示了基于经过尝试和测试的方法的解决方案。为了清楚起见,我没有使用 jQuery。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index</title>
<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/43.0.0/ckeditor5.css" />
</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(class_='ckeditor5') }}
<button type="button" class="btn-remove">Remove</button>
</div>
{% endfor -%}
</div>
<div>
{{ form.plans.label() }}
{{ form.plans(class_="ckeditor5") }}
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
<script type="importmap">
{
"imports": {
"ckeditor5": "https://cdn.ckeditor.com/ckeditor5/43.0.0/ckeditor5.js",
"ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/43.0.0/"
}
}
</script>
<script type="module">
import {
ClassicEditor,
Essentials,
Bold,
Italic,
Font,
Paragraph
} from 'ckeditor5';
const editors = {};
/**
* Create a new editor for the specified element.
*/
const createEditor = (elem) => {
ClassicEditor.create(elem, {
plugins: [ Essentials, Bold, Italic, Font, Paragraph ],
toolbar: {
items: [
'undo', 'redo', '|', 'bold', 'italic', '|',
'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor'
]
}
})
.then(editor => { editors[elem.name] = editor; })
.catch(console.error);
};
/**
* Remove the surrounding div element and destroy the associated editor.
*/
const removeEntryEl = (event) => {
if (entriesEl.childElementCount > 0) {
const elem = event.target.closest('div');
const nameAttr = elem.querySelector('textarea[name^="entries-"]').name;
if (nameAttr in editors) {
editors[nameAttr]
.destroy()
.then(() => { delete editors[nameAttr]; })
.catch(console.error);
}
elem.remove();
}
};
// Create editors for all existing text areas with a class "ckeditor5".
document
.querySelectorAll('.ckeditor5')
.forEach(createEditor);
const entriesEl = document.querySelector('#entries');
document.getElementById('btn-add').addEventListener('click', () => {
// If the button to add is clicked...
let ids = ['entries-0-bullet']
if (entriesEl.childElementCount > 0) {
// ... and there are already input fields ...
const sel = 'textarea[name^="entries-"]';
ids = Array.from(
entriesEl.lastElementChild.querySelectorAll(sel),
field => {
// ... extract the name attribute of the last input field
// and generate a new unique id from it.
return field.name.replace(
/^entries-(\d+)-(\w+)$/,
(match, p1, p2) => `entries-${parseInt(p1)+1}-${p2}`
);
});
}
// For each id created a block with the new input field.
// Register a function to remove the block and configure the CKEditor.
ids.forEach(id => {
const elem = document.createElement('div');
elem.innerHTML = `
<label for="${id}">Bullet</label>
<textarea class="ckeditor5" id="${id}" name="${id}"></textarea>
<button type="button" class="btn-remove">Remove</button>
`;
entriesEl.append(elem);
createEditor(elem.querySelector('.ckeditor5'));
elem
.querySelector('.btn-remove')
.addEventListener('click', removeEntryEl);
})
});
// Register a function to remove fields that already exist.
document.querySelectorAll('.btn-remove').forEach(btn => {
btn.addEventListener('click', removeEntryEl);
});
</script>
</body>
</html>