Changes between Initial Version and Version 1 of AjaxDojoFormSub


Ignore:
Timestamp:
Jun 11, 2006, 11:45:38 PM (18 years ago)
Author:
anonymous
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AjaxDojoFormSub

    v1 v1  
     1
     2
     3{{{
     4#!html
     5<h1>Form submission </h1>
     6}}}
     7'''Inner links'''
     8{{{
     9#!html
     10Part 1 <br/> <a href="#P1">Ajax basic form submission, Django server answers Ajax call.</a><br/>
     11Part 2 <br/><a href="#P2">Handling the form when JavaScript is deactivated.</a><br/>
     12Part 3 <br/><a href="#P3">Fixing the frozen fading when user resend the form without waiting for the first fading to end.</a>
     13}}}
     14
     15
     16{{{
     17#!html
     18<a name="P1"><h1>Ajax basic form submission, Django server answers Ajax call.</h1></a>
     19}}}
     20'''Ajax form submission using Dojo for the client side javaScript toolkit, and simpleJson for the client/server communication.'''
     21
     22This will take you through all the steps required to get started and to develop a handy form submission without reload.
     23
     24
     25== What do you need  ==
     26
     27 - Django
     28 - Dojo (v0.3) [http://dojotoolkit.org/] an open source javascript toolkit.
     29 - Simple_Json (v1.3) [http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.3/docs/index.html] used for javascript <-> python communication.
     30
     31
     32== What do we want to achieve ==
     33I will use the model of my current website, a recipe one.
     34When a registered user sees the details of a recipe, the user can rate (mark) it or update their rating (mark) by selecting the mark with a small drop down list.
     35But when the user clicks ok the whole page reloads. We will use some Ajax to make it transparent and fancy effect to show the change to the user.
     36we can add a fading status message like "Your rating has been updated".
     37The select box proposes a rating from 1 to 5, it is actually a form which is sent to the server via POST, which in django links to a method
     38in the view.py : ''def details(request)'' which does the innerwork for the details page generation.
     39
     40
     41== Installing Json ==
     42get the SimpleJson from svn.
     43
     44{{{
     45svn co http://svn.red-bean.com/bob/simplejson/trunk/ json
     46cd json
     47sudo python setup.py install
     48}}}
     49
     50== Django part ==
     51'''view.py'''
     52{{{
     53#!python
     54def details(request):
     55        [more stuff]
     56        if request.POST:
     57                # Get all the mark for this recipe
     58                # its not the best way i sould hava an oter entry or table, with total and nbr of marks for
     59                # each recipe.
     60                list_mark = Mark.objects.values('mark').filter(recipe__pk=r.id)
     61                # loop to get the total
     62                total = 0
     63                for element in list_mark:
     64                        total+= element['mark']
     65                # round it
     66                total = round((float(total) /  len(list_mark)),1)
     67                # update the total
     68                r.total_mark= total
     69                # save the user mark
     70                r.save()
     71
     72                # Now the intersting part for this tut
     73                import simple_json
     74                # it was a french string, if we dont't use unicode
     75                # the result at the output of json in Dojo is wrong.
     76                message = unicode( message, "utf-8" )
     77                #
     78                jsonList = simple_json.dumps((my_mark, total, form_message ,message))
     79                return HttpResponse(jsonList)
     80        [more stuff, if not POST return render_to_response('recettes/details.html' ...]
     81}}}
     82
     83'''url.py'''
     84
     85Just normal url.py, remember the path which will point to the wanted method.
     86{{{
     87#!python
     88from django.conf.urls.defaults import *
     89
     90urlpatterns = patterns('',
     91        [...more...]
     92        (r'^recettes/(?P<r_cat>[-\w]+)/(?P<r_slug>[-\w]+)/$', 'cefinban.recettes.views.details'),
     93        [...more...]
     94}}}
     95
     96
     97== Html template with Dojo javascript ==
     98'''Dojo use'''
     99
     100{{{
     101{% load i18n %}
     102{% extends "base.html" %}
     103{% block script %}
     104 <script type="text/javascript" src="/media/js/dojo/dojo.js"></script>
     105 <script type="text/javascript">
     106 
     107    dojo.require("dojo.widget.Tooltip");
     108    dojo.require("dojo.fx.html");
     109    dojo.require("dojo.event.*");
     110    dojo.require("dojo.json");
     111
     112    // point to the same url details.html       
     113    function sendForm()
     114    {
     115     dojo.io.bind({
     116                    url: '.',
     117                    handler: sendFormCallback,
     118                    formNode: dojo.byId('myForm')
     119                });
     120    }
     121
     122  function sendFormCallback(type, data, evt)
     123    {
     124    if (type == 'error')
     125        alert('Error when retrieving data from the server!');
     126    else
     127        // de code the simpleJson answer from django ''details'' method.
     128        // it populates a Js array ! straigth so cool !
     129        arrayData = dojo.json.evalJson(data);
     130        // now we update the html using the css id as a pointer to the part
     131        // we want to update
     132        dojo.byId("mark_total").innerHTML = arrayData[1];
     133        dojo.byId("mark_message").innerHTML = arrayData[2];
     134        dojo.byId("mark_status").innerHTML = arrayData[3];
     135        // and the fancy fading effect
     136        dojo.lfx.html.highlight("mark_status", [255, 151, 58], 700).play(300);
     137    }
     138   
     139    function init()
     140    {
     141        var sendFormButton = dojo.byId('sendFormButton');
     142        dojo.event.connect(sendFormButton, 'onclick', 'sendForm')
     143    }
     144 
     145    dojo.addOnLoad(init);
     146       
     147</script>
     148}}}
     149
     150the following ''HTML code'' just comes after the upper code snipset.
     151
     152{{{
     153[... total mark get updated, lets put css id="mark_total" ...]
     154{% if r.total_mark  %}<li><b>Score</b>: <span id="mark_total">{{ r.total_mark }}</span>/5</li>{% endif %}
     155[....]
     156{% if not user.is_anonymous %}
     157    {% ifnotequal user.id r.owner_id %}
     158        <form enctype="multipart/form-data" id="myForm" method="post">
     159            <span id="mark_message">{{mark_message}}</span>
     160               <select name="select_mark">
     161                    <option value ="1" {% ifequal my_mark 1 %} selected {% endifequal %}>1</option>
     162                    <option value ="2" {% ifequal my_mark 2 %} selected {% endifequal %}>2</option>
     163                    <option value ="3" {% ifequal my_mark 3 %} selected {% endifequal %}>3</option>
     164                    <option value ="4" {% ifequal my_mark 4 %} selected {% endifequal %}>4</option>
     165                    <option value ="5" {% ifequal my_mark 5 %} selected {% endifequal %}>5</option>
     166               </select>
     167       </form>
     168      <button id="sendFormButton">Notez</button>
     169      <br/>
     170      <span id="mark_status">{{ mark_status }}</span>
     171     {% endifnotequal %}
     172{% endif %}
     173}}}
     174
     175And, voila.
     176
     177To have a demo use guest as login, guest as password here [http://ozserver.no-ip.com:345] or if not working here [http://www.cefinban.net].
     178Go to index and pick a recipe, update the rating.
     179
     180You can also have a look at the screenshot here :  [http://ozserver.no-ip.com/~greg/images/ajaxdjango.png]
     181
     182== Dreamhost and Simplejson ==
     183
     184If you are using dreamhost for hosting please be aware that simplejson is not installed.
     185Instead you will have to install the source of simplejson in a folder in your home directory eg /proz/json/simple_json
     186The simple_json directory contains the required __init__.py for it to be loaded as a python module.
     187
     188Then in your ~/.bash_profile add the directory to your python path like below.
     189
     190{{{
     191export PYTHONPATH=$PYTHONPATH:$HOME/django/django_src:$HOME/django/django_projects:$HOME/progz/json
     192}}}
     193
     194That will allow yout to use simpl_json in python shell.
     195But '''dont forget''' to change django.fcgi !
     196Add
     197{{{
     198sys.path +=['/home/coulix/progz/json']
     199}}}
     200log out/in and try import simple_json (or simplejson depends on what source you picked)
     201
     202{{{
     203#!html
     204<a name="P2"><h1>Handling the form when JavaScript is deactivated.</h1></a>
     205}}}
     206If a user has deactivated his browser's javascript support, or is using a text mode browser, we need a way of making the previous rating button submit the rating to the server which should this time return an html template instead of data to the Ajax call.
     207
     208== Updating the form HTML (details.html template) ==
     209This time we put a submit type input inside the form instead of the button type in part 1.
     210type="submit" as indicates its name, submits the form to the server, we will need a way of stopping this behavior using javaScript.
     211
     212{{{
     213<form  enctype="multipart/form-data" id="myForm" method="post">
     214        <span id="mark_message">{{mark_message}}</span>
     215        <select name="select_mark">
     216                [...]
     217        </select>
     218        <input id="sendFormButton" type="submit" value="Notez" />
     219</form>
     220}}}
     221
     222Now, how can we tell our details method in view.py to know if it comes from a normal submit request or an Ajax request ?
     223Two solutions,
     224
     225The '''first''' uses a hidden variable in form.html and an added content element in the JS part.
     226
     227{{{
     228    function sendForm()
     229    {
     230     dojo.byId("mark_status").innerHTML = "Loading ...";
     231     dojo.io.bind({
     232                    url: '.',
     233                    handler: sendFormCallback,
     234                    content: {"js", "true"},
     235                    formNode: dojo.byId('myForm')
     236                });
     237    }
     238
     239
     240[...]
     241<form  enctype="multipart/form-data" id="myForm" method="post">
     242[...]
     243<input type="hidden" name="js" value="false">
     244</form>
     245}}}
     246
     247With this, in our django method in view.py we can test for request["js"]=="true" it would means that Js is activatd and we return the appropriate answer to the Ajax request.
     248
     249The '''second''' uses the url to pass a new variable ajax_or_not to the detail method.
     250
     251{{{
     252#!python
     253def details(request, r_slug, r_cat, ajax_or_not=None):
     254[...]
     255}}}
     256
     257We modify the url.py to accept this new parameter.
     258{{{
     259#!python
     260(r'^recettes/(?P<r_cat>[-\w]+)/(?P<r_slug>[-\w]+)/(?P<ajax_or_not>.*)$', 'cefinban.recettes.views.details'),
     261}}}
     262
     263The dojo binding needs to append a variable to the original document url, to make ajax_or_not not None.
     264
     265{{{
     266    function sendForm()
     267    {
     268     dojo.byId("mark_status").innerHTML = "Loading ...";
     269     dojo.io.bind({
     270                    url: './ajax/',
     271                    handler: sendFormCallback,
     272                    formNode: dojo.byId('myForm')
     273                });
     274    }
     275}}}
     276
     277== New details method in view.py ==
     278
     279We just need to test for the existence of ajax_or_not
     280
     281{{{
     282#!python
     283def details(request, r_slug, r_cat, ajax_or_not=None):
     284[...]
     285        if request.POST:
     286                [...] same as part 1
     287                # except here we check ajax_or_not
     288                if  ajax_or_not:
     289                        # use json for python js exchange
     290                        # it was a french string, if we dont't use unicode
     291                        # the result at the output of json in Dojo is wrong.
     292                        message = unicode( message, "utf-8" )
     293                        jsonList = simple_json.dumps((my_mark, total, form_message ,message))
     294                        return HttpResponse(jsonList)
     295
     296        return render_to_response('recettes/details.html', {'r': r, 'section':'index',
     297                 'mark_status':message , 'mark_message':form_message, 'my_mark': my_mark},
     298                  context_instance=RequestContext(request),)
     299}}}
     300
     301{{{
     302#!html
     303<a name="P3"><h1>Fixing the frozen fading when user resend the form without waiting for the first fading to end.</h1></a>
     304}}}
     305If you haven't realised yet, if two or more calls are sent to the javascript function sendForm in a short time, the fading effect of the current sendForm Callback method might get stuck / froze / bugged.
     306We need a way of avoiding this by desactivating the connection between the submit button and the sendForm method while the fading '''animation''' is active.
     307Thanks Dojo there is such things ! in two lines of code its done.
     308
     309
     310{{{
     311function sendFormCallback(type, data, evt)
     312    {
     313       [...as before ...]
     314        // and the fancy fading effect
     315       
     316        // first disconnect the listener !
     317        dojo.event.disconnect(sendFormButton, 'onclick', 'sendForm');
     318        // assign our fading effect to an anim variable.
     319        var anim = dojo.lfx.html.highlight("mark_status", [255, 151, 58], 700).play(300);
     320        // When this anim is finish, reconnect
     321        dojo.event.connect(anim, "onEnd", function() {  dojo.event.connect(sendFormButton, 'onclick', 'sendForm'); });
     322    }
     323}}}
     324
     325how nice is this !
     326Careful, while talking about how to fix the problem using onEnd in Dojo IRC chanel, they realised play() method didnt behave properly and updated it to react to onEnd and such.
     327su you need at least revision '''4286'''.
     328Update your dojo source
     329
     330{{{
     331svn co http://svn.dojotoolkit.org/dojo/trunk dojo
     332}}}
     333
     334
     335
     336
     337'''Note'''  It might be completely wrong.
     338More questions / complaints: coulix@gmail.com
     339
Back to Top