Changes between Version 30 and Version 31 of AjaxDjangoDojoForm


Ignore:
Timestamp:
06/11/2006 11:44:41 PM (9 years ago)
Author:
coulix
Comment:

organising

Legend:

Unmodified
Added
Removed
Modified
  • AjaxDjangoDojoForm

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