Skip to content

Commit

Permalink
fix #142 : FormCollection with RichtextArea widget
Browse files Browse the repository at this point in the history
  • Loading branch information
jrief committed Jun 21, 2024
1 parent b7fc05c commit 26cb213
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
JavaScript files.
* Drop support for Django-4.1 and Python-3.9.
* Add support for Python-3.12.
* Fix #142: A `FormCollection` with siblings and multiple `RichtextArea` widgets did not work.
* Attribute `<button df-click="…">` now accepts function `setFieldValue()`. This can be used to
transfer values from one field to another one.
* Introduce partial submits and prefilling of dialog forms in collections.
Expand Down
73 changes: 27 additions & 46 deletions client/django-formset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ window.addEventListener('DOMContentLoaded', (event) => {
const promises = Array<Promise<void>>();

function defineComponent(resolve: Function, selector: string, newComponent: CustomElementConstructor, options: ElementDefinitionOptions|undefined=undefined) {
if (customElements.get(selector) instanceof Function) {
resolve();
} else {
window.customElements.whenDefined(selector).then(() => resolve());
if (!(window.customElements.get(selector) instanceof Function)) {
window.customElements.define(selector, newComponent, options);
window.customElements.whenDefined(selector).then(() => resolve());
}
}

function domLookup(fragmentRoot: Document|DocumentFragment) {
function domLookup(fragmentRoot: Document|DocumentFragment, isTemplate: boolean=false) {
// remember to always reflect imports below here also in django-formset.monolith.ts
if (fragmentRoot.querySelector('select[is="django-selectize"]')) {
promises.push(new Promise((resolve, reject) => {
Expand All @@ -32,31 +30,19 @@ window.addEventListener('DOMContentLoaded', (event) => {
}).catch(err => reject(err));
}));
}
if (fragmentRoot.querySelector('django-sortable-select')) {
if (fragmentRoot.querySelector('select[is="django-dual-selector"], django-sortable-select')) {
promises.push(new Promise((resolve, reject) => {
import('django-formset/SortableSelect').then(({SortableSelectElement}) => {
if (customElements.get('django-sortable-select') instanceof Function) {
resolve();
} else {
import('django-formset/DualSelector').then(({DualSelectorElement, SortableSelectElement}) => {
Promise.all([
window.customElements.whenDefined('django-sortable-select'),
window.customElements.whenDefined('django-dual-selector'),
]).then(() => resolve());
if (!(window.customElements.get('django-sortable-select') instanceof Function)) {
window.customElements.define('django-sortable-select', SortableSelectElement);
}
import('django-formset/DualSelector').then(({DualSelectorElement}) => {
if (customElements.get('django-dual-selector') instanceof Function) {
resolve();
} else {
window.customElements.define('django-dual-selector', DualSelectorElement, {extends: 'select'});
Promise.all([
window.customElements.whenDefined('django-sortable-select'),
window.customElements.whenDefined('django-dual-selector'),
]).then(() => resolve());
}
}).catch(err => reject(err));
}).catch(err => reject(err));
}));
} else if (fragmentRoot.querySelector('select[is="django-dual-selector"]')) {
promises.push(new Promise((resolve, reject) => {
import('django-formset/DualSelector').then(({DualSelectorElement}) => {
defineComponent(resolve, 'django-dual-selector', DualSelectorElement, {extends: 'select'});
if (!(window.customElements.get('django-dual-selector') instanceof Function)) {
window.customElements.define('django-dual-selector', DualSelectorElement, {extends: 'select'});
}
}).catch(err => reject(err));
}));
}
Expand All @@ -67,30 +53,25 @@ window.addEventListener('DOMContentLoaded', (event) => {
}).catch(err => reject(err));
}));
}
const textareaElements = fragmentRoot.querySelectorAll('textarea[is="django-richtext"]');
textareaElements.forEach(textareaElement => {
if (fragmentRoot.querySelector('textarea[is="django-richtext"]')) {
promises.push(new Promise((resolve, reject) => {
const resolveWhenConnected = () => {
// RichtextArea connects asynchronously, so we need to wait until it is connected to the DOM
if ((textareaElement as any).isInitialized) {
// <textarea is="django-richtext"> is already connected and initialized
resolve();
} else {
// <textarea is="django-richtext"> is waiting to be connected and initialized
textareaElement.addEventListener('connected', () => resolve(), {once: true});
}
};

import('django-formset/RichtextArea').then(({RichTextAreaElement}) => {
if (customElements.get('django-richtext') instanceof Function) {
resolveWhenConnected();
} else {
const textareaElements = fragmentRoot.querySelectorAll('textarea[is="django-richtext"]');
window.customElements.whenDefined('django-richtext').then(() => {
Promise.all(Array.from(textareaElements).map(textareaElement => new Promise<void>(resolve => {
if (isTemplate || (textareaElement as any).isInitialized) {
resolve();
} else {
textareaElement.addEventListener('connected', () => resolve(), {once: true});
}
}))).then(() => resolve());
}).then(() => resolve());
if (!(window.customElements.get('django-richtext') instanceof Function)) {
window.customElements.define('django-richtext', RichTextAreaElement, {extends: 'textarea'});
window.customElements.whenDefined('django-richtext').then(resolveWhenConnected);
}
}).catch(err => reject(err));
}));
});
}
if (fragmentRoot.querySelector('input[is="django-slug"]')) {
promises.push(new Promise((resolve, reject) => {
import('django-formset/DjangoSlug').then(({DjangoSlugElement}) => {
Expand Down Expand Up @@ -186,7 +167,7 @@ window.addEventListener('DOMContentLoaded', (event) => {

document.querySelectorAll('template.empty-collection').forEach(element => {
if (element instanceof HTMLTemplateElement && element.content instanceof DocumentFragment) {
domLookup(element.content);
domLookup(element.content, true);
}
});
domLookup(document);
Expand Down
20 changes: 8 additions & 12 deletions client/django-formset/RichtextArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -990,12 +990,12 @@ class RichtextArea {
private focused = () => {
this.wrapperElement.classList.add('focused');
this.textAreaElement.dispatchEvent(new Event('focus'));
}
};

private updated = () => {
this.textAreaElement.innerHTML = this.editor.getHTML();
this.textAreaElement.dispatchEvent(new Event('input'));
}
};

private blurred = () => {
this.registeredActions.forEach(action => action.deactivate());
Expand All @@ -1008,19 +1008,19 @@ class RichtextArea {
this.wrapperElement.classList.remove('invalid');
}
this.textAreaElement.dispatchEvent(new Event('blur'));
}
};

private contentUpdate = () => {
if (this.charaterCountDiv && this.characterCountTemplate) {
const context = {count: this.editor.storage.characterCount.characters()};
this.charaterCountDiv.innerHTML = this.characterCountTemplate(context);
}
}
};

private selectionUpdate = () => {
this.registeredActions.forEach(action => action.activate(this.editor));
this.formDialogs.forEach(dialog => dialog.activate(this.editor));
}
};

private formResetted = () => {
const chain = this.editor.chain().clearContent();
Expand All @@ -1032,9 +1032,9 @@ class RichtextArea {
}
chain.run();
}
}
};

private formSubmitted = () => {}
private formSubmitted = () => {};

private attributesChanged(mutationsList: Array<MutationRecord>) {
for (const mutation of mutationsList) {
Expand Down Expand Up @@ -1169,11 +1169,7 @@ export class RichTextAreaElement extends HTMLTextAreaElement {
return this[RA].getValue();
}

public get isInitialized() : boolean {
return this[RA].isInitialized;
}

public updateOperability(...args: any[]) : void {
updateOperability(...args: any[]) : void {
this[RA].updateOperability(...args);
}
}
17 changes: 3 additions & 14 deletions testapp/forms/gallerycollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
from django.forms.models import ModelForm

from formset.collection import FormCollection
from formset.richtext.dialogs import SimpleLinkDialogForm
from formset.richtext.widgets import RichTextarea, controls
from formset.widgets import UploadedFileInput

from testapp.models.gallery import Image, Gallery
Expand All @@ -14,22 +12,13 @@ class ImageForm(ModelForm):
required=False,
widget=widgets.HiddenInput,
)
image = fields.FileField(
label="Image",
widget=UploadedFileInput,
required=False,
)
caption = fields.CharField(
label="Caption",
required=False,
widget=RichTextarea(
#attrs={'use_json': True},
)
)

class Meta:
model = Image
fields = ['id', 'image', 'caption']
widgets = {
'image': UploadedFileInput,
}


class ImageCollection(FormCollection):
Expand Down

0 comments on commit 26cb213

Please sign in to comment.