Question

There are quite a few moving parts to this question, but if you have any insight to any piece of it, it would be appreciated.

I want to build a feedback form that acts as one would expect. When the user clicks the feedback button at the bottom right of the page, it launches a bootstrap modal. The modal has a django crispy form that submits or returns the fields that are invalid when the submit button is pressed.

First, I have my feedback button:

{% load crispy_forms_tags %}

.feedback-button {
    position: fixed;
    bottom: 0;
    right: 30px;
}

<div class='feedback-button'>
    <a class="btn btn-info" href="#feedbackModal" data-toggle="modal" title="Leave feedback" target="_blank">
        <i class="icon-comment icon-white"></i>
        Leave feedback
    </a>
</div>
<div class="modal hide" id="feedbackModal" tabindex="-1" role="dialog" aria-labelledby="feedbackModalLabel" aria-hidden="true">
    <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
        <h3 id="feedbackModalLabel">Contact Form</h3>
    </div>
    <div class="modal-body">
        {% crispy feedback_form feedback_form.helper %}
    </div>
    <div class="modal-footer">
        <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
        <button class="btn btn-primary">Submit</button>
    </div>
</div>

Next, I have my form:

class Feedback(models.Model):
    creation_date = models.DateTimeField("Creation Date", default=datetime.now)
    topic = models.CharField("Topic", choices = TOPIC_CHOICES, max_length=50)
    subject = models.CharField("Subject", max_length=100)
    message = models.TextField("Message", blank=True)
    sender = models.CharField("Sender", max_length=50, blank=True, null=True)

    def __unicode__(self):
        return "%s - %s" % (self.subject, self.creation_date)

    class Meta:
            ordering = ["creation_date"]
        verbose_name = "Feedback"
        verbose_name_plural = "Feedback"

class Crispy_ContactForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
                Field('topic', placeholder='Topic', css_class='input-medium'),
                Field('subject', placeholder='Subject', css_class='input-xlarge'),
                Field('message', placeholder='Message', rows='5', css_class='input-xlarge'),
                Field('sender', placeholder='Sender', css_class='input-xlarge'),
            ),
        )
        self.helper.form_id = 'id-Crispy_ContactForm'
        self.helper.form_method = 'post'

        super(Crispy_ContactForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Feedback
        exclude = ['creation_date']

I tried to omit the legend in the crispy form because if I include it, the modal appears to have two form titles. But omitting the legend in the crispy form layout resulted in the fields appearing out of order.

So I'm left with a few questions:

  1. Overall, am I going about this the right way?
  2. If I hook up the modal's submit button to AJAX, how do I go about error checking the form?
  3. Is there a better way to display the crispy form in the bootstrap modal?
Was it helpful?

Solution

I found a partial solution on this page. In my base template, I created the button and the form:

<div class='feedback-button'><a class="btn btn-info" href="#feedbackModal" data-toggle="modal" title="Leave feedback" target="_blank"><i class="icon-comment icon-white"></i> Leave feedback</a></div>
{% include "_feedback_form.html" with feedback_form=feedback_form %}

Then I created two feedback forms

<div class="modal hide" id="feedbackModal" tabindex="-1" role="dialog" aria-labelledby="feedbackModalLabel" aria-hidden="true">
    <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
        <h3 id="feedbackModalLabel">Contact Form</h3>
    </div>
    {% include "_feedback_form_two.html" with feedback_form=feedback_form %}
</div>

and

{% load crispy_forms_tags %}

<form action="{% url feedback %}" method="post" id="id-Crispy_ContactForm" class="form ajax" data-replace="#id-Crispy_ContactForm">
    <div class="modal-body">
    {% crispy feedback_form %}  
    </div>
    <div class="modal-footer">
        <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
        <input type="submit" name="submit_feedback" value="Submit" class="btn btn-primary" id="submit-id-submit_feedback" />
    </div>
</form>

I broke the feedback forms into two because the bootstrap-ajax.js file that I'm leveraging from the above link replaces the html from the one template. If I use a combined feedback form, it will have class="modal hide". I need it to just have class="modal" so that if the form is refreshing with errors, the modal doesn't disappear.

In my view, I have

@login_required
def feedback_ajax(request):
    feedback_form = Crispy_ContactForm(request.POST)
    dismiss_modal = False
    if feedback_form.is_valid():
        message = feedback_form.save()
        feedback_form = Crispy_ContactForm()
        dismiss_modal = True
    data = {
        "html": render_to_string("_feedback_form_two.html", {
            "feedback_form": feedback_form
        }, context_instance=RequestContext(request)),
        "dismiss_modal": dismiss_modal
    }
    return HttpResponse(json.dumps(data), mimetype="application/json")

And then in the bootstrap-ajax.js file (again from the above link), I made a few alterations. In the processData function, I defined:

var $el_parent = $el.parent();

and I added

if (data.dismiss_modal) {
    var msg = '<div class="alert alert-success" id="' + $(replace_selector).attr('id') + '">Feedback Submitted</div>'
    $(replace_selector).replaceWith(msg);
    $el_parent.modal('hide');
    $(replace_selector).replaceWith(data.html);
}

This isn't fully functional yet because the Success Message disappears with the modal immediately. I want the modal to display the message and disappear after maybe 3 seconds. haven't figured this out yet, but it works well enough for now.

I'm still tinkering, but this addresses most of my questions:

It submits data with AJAX and returns with error checking if needed. The form displays fairly well in the modal.

I have a few remaining issues. I need to figure out a way to suppress the legend in the crispy form, and I need to find a way to display the modal crispy form and not interfere with another crispy form that appears elsewhere on the site.

OTHER TIPS

I answered a similar question to this on a related question.

https://stackoverflow.com/a/12905016/1406860

This will get you everything except the return of errors.

I would suggest doing the validation and creating an 'errors': 'List of problems' entry in the dictionary that is fed back and check for this in the AJAX success as per whether to close the modal (because there weren't errors) or displaying errors as appropriate.

JD

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top