Skip to content
Advertisement

Django/Js: how to post a form without reloading whole page

My application currently flows through 3 pages:

  1. User selects question in index page
  2. User submits answer in answer page
  3. User is presented with result in results page.

I want to compress that down to a single page where the user submits an answer to the question and result is shown on the same page.

The following django-template code separates questions with Bootstrap accordion. How do I post the form without refreshing the whole page? I want to be able to display the result on the page, update CSS styling with Javascript etc.

<h2>{{ category.title }}</h2>
    <div class="accordion" id="accordion{{category.title}}">

      {% for challenge in category.challenge_set.all %} 
      <div class="card">
        <div class="card-header" id="heading{{challenge.id}}">
          <h2 class="mb-0">
            <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapse{{challenge.id}}" aria-expanded="true" aria-controls="collapse{{challenge.id}}">
              {{ challenge.question_text }} - {{ challenge.point_value }} points
            </button>
          </h2>
        </div>
        <div id="collapse{{challenge.id}}" class="collapse in" aria-labelledby="heading{{challenge.id}}" data-parent="#accordion{{category.title}}">
          <div class="card-body">
            
            <p>{{ challenge.description }}</p>
          
            <form action="{% url 'challenges:answer' challenge.id %}" method="post">
            
              {% if challenge|is_answered:request %}
                <label for="answered">Answer</label>
                <input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
              {% else %}
              {% csrf_token %}
                <label for="answer">Answer</label>
                <input type="text" name="answer" id="answer">
                <input type="submit" value="Submit">
              {% endif %}
            </form>
        </div>
      </div>
      {% endfor %}

    </div>

Here is the view:

def index(request):
    context = {'challenges_by_category_list': Category.objects.all()}
    return render(request, 'challenges/index.html', context)

def detail(request, challenge_id):
    challenge = get_object_or_404(Challenge, pk=challenge_id)
    return render(request, 'challenges/detail.html', {'challenge': challenge})

def results(request, challenge_id, result):
    challenge = get_object_or_404(Challenge, pk=challenge_id)
    return render(request, 'challenges/results.html', {'challenge':challenge, 'result':result})

def answer(request, challenge_id):
    challenge = get_object_or_404(Challenge, pk=challenge_id)
    result = "Incorrect, try again!"
    if challenge.answer_text.lower() == request.POST['answer'].lower():
      current_user = request.user
      session = User_Challenge(user=current_user, challenge=challenge, answered=True)
      session.save()
      points = Profile(user=current_user, points=challenge.point_value)
      points.save()
      result = "Correct!"
    return HttpResponseRedirect(reverse('challenges:results', args=(challenge.id, result)))

Advertisement

Answer

You can try this:

Add the below script in your template:

<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>

write a script and a function inside it to submit the form data.

<script type="text/javascript">
    function submitData( challenge_id ){
        // Get answer from the input element
        var answer = document.getElementById("answer").value;

        // add the url over here where you want to submit form & challenge_id is also taken as a parameter.
        var url = "<your_url>";

        $.ajax({
            url: url,
            data: {
                'answer': answer,
            },
            dataType: 'JSON',
            success: function(data){
                // show an alert message when form is submitted and it gets a response from the view where result is provided and if url is provided then redirect the user to that url.
                alert(data.result);
                if (data.url){
                   window.open(data.url, '_self');
                }
            }
        });
    }
</script>

Change type of the submit button and add an onclick event to call the submitData() function and pass the challenge id to it. And remove the action attr from the form. see below:

<form method="post">
    {% csrf_token %}
    {% if challenge|is_answered:request %}
        <label for="answered">Answer</label>
        <input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
    {% else %}
        <label for="answer">Answer</label>
        <input type="text" name="answer" id="answer">
        // over here
        <button type="button" onclick="submitData({{ challenge.id }})">
            Submit
        </button>
    {% endif %}
</form>

Return a JsonReponse to the ajax call from the views.

views.py

def answer(request, challenge_id):
    answer = request.GET.get('answer', False)
    url = False
    if challenge.objects.filter(id=challenge_id).exists() and answer:
       challenge = Challenge.objects.get(id=challenge_id)
       if challenge.answer_text.lower() == answer.lower():
          current_user = request.user
          session = User_Challenge(user=current_user, challenge=challenge, answered=True)
          session.save()
          points = Profile(user=current_user, points=challenge.point_value)
          points.save()
          result = "Correct!"
  
          # specify the url where you want to redirect the user after correct answer
          url = "" 
    else:
       result = "Incorrect, try again!"
    data = {
       'result': result,
       'url': url
    }
    return JsonResponse(data)
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement