Reputation: 169
I am trying to submit a Django form with AJAX and Vanilla JavaScript however the form is not actually submitting unless I click the submit button twice.
I have an event listener on the form that stops the default submission to avoid page reload and then I open a XMLHttpRequest. On the first submission I get a 200 response but the data hasn't actually been sent to the database. However if I click the submit button again I get the desired 201 (item created) response from the server and it reloads my posts and adds the new one perfectly.
I am still a bit unfamiliar on working with asynchronous data and cannot figure out why it's not working. If I remove the e.preventDefault the form submits correctly and the new post shows up after the page reloads.
relevant JS snippet:
const postCreateFormEl = document.getElementById("post-create-form")
const postsEl = document.getElementById("posts")
const handlePostSubmit = function(e){
e.preventDefault()
const myForm = e.target
const myFormData = new FormData(myForm)
const url = myForm.getAttribute("action")
const method = myForm.getAttribute("method")
const xhr = new XMLHttpRequest()
xhr.open(method, url)
xhr.setRequestHeader("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
xhr.onload = function() {
const serverResponse = xhr.response
console.log(serverResponse, xhr.status)
const postsEl = document.getElementById("posts")
loadPosts(postsEl)
}
xhr.send(myFormData)
}
postCreateFormEl.addEventListener("submit", handlePostSubmit)
django views snippet:
def home_view(request, *args, **kwargs):
form = PostForm()
return render(request, "pages/home.html", context={'form': form})
def post_create_view(request, *args, **kwargs):
form = PostForm(request.POST or None)
next_url = request.POST.get("next") or None
if form.is_valid():
obj = form.save(commit=False)
obj.save()
if request.is_ajax():
return JsonResponse({"success": "Object created"}, status=201)
if next_url != None and is_safe_url(next_url, ALLOWED_HOSTS):
return redirect(next_url)
form = PostForm()
return render(request, 'components/form.html', context={"form": form})
On the first submit in the console it returns the event target and on the second on it returns a jsonresponse as intended. Any direction is appreciated!
edit:
home.html:
<form class='form' id='post-create-form' method='POST' action='/create-post'>
<input type='hidden' value='/' name='next'/>
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<input type='submit' value='submit'>
</form>
urlpatterns:
urlpatterns = [
path('admin/', admin.site.urls),
path('ckeditor/', include('ckeditor_uploader.urls')),
path('', home_view),
path('posts/<int:post_id>', post_detail_view),
path('posts', post_list_view),
path('create-post', post_create_view),
]
forms.py:
class PostForm(forms.ModelForm):
content = forms.CharField(widget=CKEditorUploadingWidget())
class Meta:
model = Post
fields = '__all__'
def clean_content(self):
content = self.cleaned_data.get("content")
if len(content) > MAX_POST_LENGTH:
raise forms.ValidationError("This post is too long")
return content
Upvotes: 1
Views: 128
Reputation: 21782
Most WYSIWYG editors don't edit the actual input element that will be submitted when the user gives input. Instead what they do is that they attach an event on the submission of such forms and then when the form is being submitted they set the value of the input. CKEditor is no exception to this and does the same.
Hence what is happening here is that you submit the form and your on submit handler fires first. Unfortunately this means that the input is never filled when you submit the form by ajax, but the input does get filled after your ajax call. Which is why your second submit is successful.
What you can do to resolve this is to make CKEditor update the values of your input on submission yourself. Modify your script like so and also make sure that the your script is somewhere below where you load the forms media:
const handlePostSubmit = function(e){
e.preventDefault()
for (instance in CKEDITOR.instances) {
CKEDITOR.instances[instance].updateElement();
}
// Rest of your function
}
Upvotes: 2