views:

453

answers:

4

The other day I wrote some AJAX for a Django app that i have been working on.

I come from Ruby on Rails, so I haven't done much in the way of raw JS.

So based on Rails' partials, I something similar to the following in a sort of pseudocode, don't sweat the details:

1) JS function using prototype's Ajax.Updater ('tablediv' being the id of the table i wanted to update Ajaxily, and url pointing to the proper django view)

 function updateTable(){
       new Ajax.Updater('tablediv',url {params: params....etc

2) django view that got new data to populate the table with:

 def ajaxTable
     objects = Objects.object.all...
     return render_to_response('ajaxtable.html',objects)

3) ajaxtable.html was just a sort of Rails "partial" so basically a table w/o <table> </table> ...:

   <th>{{object.data}}</th>
   <td>{{object.moredata}}</td>

so to my actual question:

This seemed hacky to me, I sort of threw it together after getting tired of searching online for what i wanted.

Is this the way it's done? It works fine, I just don't know enough to know, you know?

+1  A: 

What exactly seems hacky about it? Seems like a perfectly valid way of doing something.

I guess an alternative would be serialising to json and sending it back to a javascript templating snippet.

Steerpike
That is what i had played around with, but at the time I didnt know as much javascript as I do know, so just send the objects as JSON, make javascript object literals out of it, and then tablediv.innerHTML = foo?
Joel
JSON *is* an object literal so the nice thing about using it in javascript is that you don't have to do anything with it to actually use it once you have it. I like using JSON in conjunction with the templating system I linked above (jquery, not prototype, sorry) as you can just pass the JSON returned straight to the template you setup and it'll work.
Steerpike
+2  A: 

No matter what, you're going to need at least two things:

  1. Your javascript code to make the call (you have this)

  2. Server side code to handle the request (this is your view and url-config)

There is absolutely nothing "hacky" about this.

The third thing, your template file, is optional - but is generally good practice. You want to separate your markup from the code, for many reasons.

So I think you've got the right idea. Carry on.

Fragsworth
nice, thanks for the response, I finished the project and moved on, but it has kinda bugged me since then (when I am in the middle of something I can't stop to ask on a forum or stack overflow..I don't have the patience :),good to know that my instincts aren't completely ridiculus
Joel
+4  A: 

It kinda depends what you want to do I think. Ajax being quite a wide range of scenarios from Google Maps to a simple auto-complete varys greatly in complexity and the best approach.

However, there are some useful things you can do that help.

1) Template level

Make sure you have "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS setting. Then you can do this;

{% if not request.is_ajax %}
<html>
  <head>
  ...
  </head>
  <body>
  ...
{% endif %}
actual content
{% if not request.is_ajax %}
</body>
</html>
{% endif %}

Basically then say this page is /test/ you can do a browser request and get the full content or a request via JavaScript and just get the content. There is a blogpost somewhere that explains this in more detail but I can't find it at the moment.

2) In the view

In the template we are just accessing the request object in the template. In the view you can do very similar things.

def my_view(request):
    if requst.is_ajax():
        # handle for Ajax requests

    # otherwise handle 'normal' requests
    return HttpResponse('Hello world')

The above methods don't really do it differently than you do but allow you to re-use views and write it bit more concisely. I wouldn't really say what you are doing is wrong or hacky but you could write it to make it more concise and re-use the templates and views.

say for example you could have just one template and if its a Ajax request have it only return the section that will need to be updated. In your case it would be the tables views.

Orange Box
That seems really clever, coming from using embedded ruby that appeals to me...maybe <em>too</em> clever...
Joel
haha no html in the comments...
Joel
too clever? I really quite like it. You can fully Ajaxify large bits of a website quickly, unfortunately there is no way to have more than one 'ajax block' per template... hmm I wonder how you could do that.
Orange Box
+2  A: 

I am quite late, but I want to document how to combine and adapt the solutions presented by d0ugal in a way, that it will resolve a much cleaner template-code.

I have a model representing contact persons.

The (generic) view to get one ContactPerson looks like this:

def contcactperson_detail_view(request, name):
    try:
        person = ContactPerson.objects.get(slug=name)
    except:
       raise Http404
    if request.is_ajax():
        return contcactperson_detail_view_ajax(request, person)
    return list_detail.object_detail(
            request,
            queryset = ContactPerson.objects.all(),
            object_id = person.id,
            template_object_name = "contactperson",
        )

@render_to('cms/contactperson_detail_ajax.html')    
def  contcactperson_detail_view_ajax(request, person):
    return {'contactperson':person, 'is_ajax':True}

The template to render the view that handles one ContactPerson is called contcactperson_detail_view.html:

{% extends "index.html" %}
{% block textpane %}

<h1 id="mainheader">{{ contactperson.first_name }} {{ contactperson.family_name  }} </h1>
<div class="indentation">&nbsp;</div> 
{% include 'cms/contactperson_detail_photo.html' %}                                                                                                          
<div id="text_pane">

{% include 'cms/contactperson_detail_textpane.html' %}
</div>
{% endblock %}

It includes two sub-templates

contactperson_detail_textpane.html


<p>{{ contactperson.description }}</p>
<ul>
    <li>
        <dl>
            <dt>Email</dt>
            <dd>
                {{ contactperson.mail }}
            </dd>
        </dl>
    </li>
    <li>
        <dl>
            <dt>Contact Person for</dt>
            <dd>
                <ul>
                {% for c in contactperson.categories.all %}
                    <li><a href="{% url category-view c.slug %}">{{ c }}</a></li>
                {% endfor %}
                </ul>
            </dd>
        </dl>
    </li>
</ul>

and contactperson_detail_photo.html

{% with contactperson.photo.detailphoto as pic %} 
    {% with pic.url as pic_url %}    
    <div {% if not is_ajax %}id='imageContainer'{% endif %} style="float: right;padding-right:0.5em; 
                                    padding-bottom: 1em; padding-left:0.5em;clear:both; 
                                    width:{{ pic.width }}px">   
        <div style="width:{{ pic.width}}px">                       
                <img style="clear:both" src="{{ pic_url }}" alt="{{ i.name }}"/> 
        </div>                                                                                                                    
    </div>   
    {% endwith %}
{% endwith %}

this 3 templates will be used, if the request isn't ajax.

But if the request is ajax, contcactperson_detail_view will return the view contcactperson_detail_view_ajax, that uses the template contactperson_detail_ajax.html for rendering. And this template looks like this:

<h1>{{ contactperson.first_name }} {{ contactperson.family_name  }}</h1>
{% include 'cms/contactperson_detail_photo.html' %}                                                                                                          
{% include 'cms/contactperson_detail_textpane.html' %}

So it uses the same sub-templates but isn't extending anything, therefore only the needed markup delivered. As the ajax view passes is_ajax = True to the template, it can be used to adjust minor things, like setting correct id-attributes.

No context-processor or additional url-conf needed.

Finally the Javascript code:

$("#contact_person_portlet a").click(function(event){
       event.preventDefault();
       $.ajax({
           type: "GET",
           url: event.target.getAttribute('href'),
           success: function(msg){
               overlay(msg);
           }
        });
    });

Hope that it will be useful for some people. If so, please leave a comment!

vikingosegundo