!

Dette materialet blir ikke lenger vedlikeholdt. Du vil finne oppdatert materiale på siden: http://borres.hiof.no/wep/

AJAX
Database
POST
GET
Børre Stenseth
Eksempler >IFA

IFA

Hva
Slagord for IFA-pastiller, en blog

Temaet er AJAX, Python og databaser. Hensikten er å lage en blog-lignende vevside som lar brukere legge inn slagord for IFA-pastiller, kommentere andres forslag og stemme på det slagordet de liker best.

Løsningen er prinsippielt slik:

struktur
Dataflyt IFA

Det er 6 brukerfunksjoner som er implementerte. En oversikt over funksjon og respons:

(Re)load siden Hent og vis alle ordtak
Registrer nytt ordtak Hent og vis alle ordtak
Stem på et ordtak Hent og vis alle ordtak
Se komentarene for et ordtak Hent og vis det aktuelle ordtaket med kommentarer
Skjul komentarene for et ordtak Hent og vis det aktuelle ordtaket uten kommentarer
Registrer en kommentar for et ordtak Hent og vis det aktuelle ordtaket med kommentarer

Javascript

Javascript-koden som går på vevsiden er slik:

var myRequest;
var busy=false;
var currentSID=1;
var scriptpath=
 'http://www.it.hiof.no/~borres/cgi-bin/ajax/ifax/ajaxscript.py';
//-------------------------------------------------------
// general attempt to perform a request, if none is busy already
function getRequestObject()
{
    if(busy)
        return;
    if (window.XMLHttpRequest)
        myRequest = new XMLHttpRequest();
    else if (window.ActiveXObject)
    {
        try { myRequest = new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch(e) {
            try {myRequest= new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(e) {myRequest=null;}
        }
    }
    else
        myRequest=null;
}
//-----------------------------------------
// add a dummy random parameter to force refresh
// in Internet Explorer
function addXtra(params)
{
    var now=new Date();
    s='&xtra='+now.getMinutes()+''+now.getSeconds()+''+now.getMilliseconds();
    return params+s;
}

//-----------------------------------------------------------
// controlling and formatting parameterlists:
function constructSloganParams(slogan,author)
{
    if (slogan.length<10)
    {
        alert("Du må lage et skikkelig ordtak");
        return '';
    }
    if(author.length==0)
    {
        alert("Du må fylle ut hvem som har laget ordtaket");
        return '';
    }
    // we will use them
    slogan=slogan.replace(/\r\n/g,'\n');
    slogan=slogan.replace(/\n/g,'<br/>');
    var par='what=newslogan&slogan='
           +escape(slogan)+'&author='+escape(author);
    return par;
}
function constructCommentParams(comment,author,sid)
{
    if (comment.length<2)
    {
        alert("Du må lage en skikkelig kommentar");
        return '';
    }
    if(author.length==0)
    {
        alert("Du må fylle ut hvem som har laget kommentaren");
        return '';
    }
    // we will use them
    comment=comment.replace(/\r\n/g,'\n');
    comment=comment.replace(/\n/g,'<br/>');
    var par='what=newcomment&comment='+
        escape(comment)+'&author='+escape(author)+'&slogan='+sid;
    return par;
}

//-------------------------------------------
// process returns from requests
// place the whole slogan package
function prosessAllSlogans()
{
    // if the request is complete and successfull
    if (myRequest.readyState == 4) {
    if ((myRequest.status == 200) || (myRequest.status == 304))
    {
        elt=document.getElementById("allslogans");
        elt.innerHTML=myRequest.responseText;
    }
    else
        alert("Problem med tilgang til data:\n" + myRequest.statusText);
    busy=false;
    }
}
// place one slogan
function prosessOneSlogan()
{
    // if the request is complete and successfull
    if (myRequest.readyState == 4) {
    if ((myRequest.status == 200) || (myRequest.status == 304))
    {
        elt=document.getElementById("slogan"+currentSID);
        elt.innerHTML=myRequest.responseText;
    }
    else
        alert("Problem med tilgang til data:\n" + myRequest.statusText);
    busy=false;
    }
}

//------------------------------------
// functions and requests
// request the whole sloganpackage
// using get
function loadAllSlogans()
{
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessAllSlogans;
        params='what=allslogans';
        params=addXtra(params);
        myRequest.open("GET", scriptpath+'?'+params, true);
        myRequest.send(null);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}
// register a new slogan and request the whole slogans package
// using post
function handleNewSlogan(form)
{
    var params=
      constructSloganParams(form.new_slogan.value,
                            form.new_slogan_author.value);
    if (params.length==0)
      return;
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessAllSlogans;
        params=addXtra(params);
        myRequest.open("POST",scriptpath , true);
         myRequest.setRequestHeader("Content-type",
            "application/x-www-form-urlencoded");
          myRequest.setRequestHeader("Content-length", params.length);
          myRequest.setRequestHeader("Connection", "close");
          myRequest.send(params);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}
//-----------------------------------------
// control voting, only one vote pr slogan
// using get
function handleVote(sid)
{
    // only one vote pr slogan
    elt=document.getElementById("voted");
    v=elt.innerHTML;
    mark=("("+sid+")")
    if (v.indexOf(mark) != -1)
    {
        alert("Du har allerede stemt på denne");
        return;
    }
    elt.innerHTML=v+mark;
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessAllSlogans;
        params='what=vote&slogan='+sid;
        params=addXtra(params);
        myRequest.open("GET", scriptpath+'?'+params, true);
        myRequest.send(null);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}
//------------------------------------------
// expand and collapse comments, get the actual slogan
// using get
function expandComment(sid)
{
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessOneSlogan;
        params='what=expand&slogan='+sid;
        params=addXtra(params);
        currentSID=sid;
        myRequest.open("GET", scriptpath+'?'+params, true);
        myRequest.send(null);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}
function collapseComment(sid)
{
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessOneSlogan;
        params='what=collapse&slogan='+sid;
        params=addXtra(params);
        currentSID=sid;
        myRequest.open("GET", scriptpath+'?'+params, true);
        myRequest.send(null);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}
// register a new comment and request the commented slogan
// using post
function handleNewComment(form,sid)
{
    params=
      constructCommentParams(form.new_comment.value,
                             form.new_comment_author.value,sid);
    if (params.length==0)
        return;
    getRequestObject();
    if (myRequest) {
        myRequest.onreadystatechange = prosessOneSlogan;
        params=addXtra(params);
        currentSID=sid;
        myRequest.open("POST",scriptpath , true);
         myRequest.setRequestHeader("Content-type", 
                      "application/x-www-form-urlencoded");
          myRequest.setRequestHeader("Content-length", params.length);
          myRequest.setRequestHeader("Connection", "close");
          myRequest.send(params);
        busy=true;
    }
    else{
        alert("Nettleseren henger ikke med");
    }
}

Det er flere ting å merke seg med skriptet:

  1. Det bruker både GET og POST.
    En GET er som vanlig, etter at vi har preparert parameterlista:
    myRequest.open("GET", scriptpath+'?'+params, true);
    myRequest.send(null);
    		
    En POST er slik, etter at vi har preparert parameterlista:
    myRequest.open("POST",scriptpath , true);
    myRequest.setRequestHeader("Content-type", 
                    "application/x-www-form-urlencoded");
    myRequest.setRequestHeader("Content-length", params.length);
    myRequest.setRequestHeader("Connection", "close");
    myRequest.send(params);
    		
    Det spiller ingen rolle for Pythonskriptet på serveren hvilken overføringsmetode vi bruker.
  2. Parametrene kodes med escape()
    Det vi oppnår med dette er at alle ikke-ascii tegn kodes i formen %nn hvor nn er et 2-sifret hexadesimalt tall. Denne formen fører til at vi ikke får noen misforståelser med hva som er hva i parameterlista. Denne kodingen er nødvendig i dette eksempelet fordi vi skal kunne overføre norske tegn.
  3. Vi legger til en "random" parameter, function addXtra(params). Vitsen med dette er å hindre Internet Explorer å hente den gamle cashede versjonen av siden.
  4. Vi handterer kun en request om gangen. Dette forenkler logikken på siden. Vi kan f.eks. huske et ordtaksnummer fra forespørselen er gitt til vi får svaret. I dette tilfellet er dette neppe noen begrensning i forhold til brukeren.
  5. Vi husker hvem vi har stemt på. Nederst på siden ligger et skjult felt som oppdateres fra Javascriptet:
    <div id="voted" style="display:none">Stemt på: </div>
    		
  6. Vi bruker regulære uttrykk i replace- for å erstatte alle forekomster at en tegnesekvens.
    slogan=slogan.replace(/\r\n/g,'\n');
    slogan=slogan.replace(/\n/g,'<br/>');
    		

Pythonskriptet

Pythonskriptet på tjeneren er slik:

#! /usr/bin/python2.5
# -*- coding: latin_1 -*-
import MySQLdb,cgi,urllib,datetime
import cgitb; cgitb.enable()
"""
Retrieve and update database diverse.
    xslogan
        slogan_id BIGINT PRIMARY KEY,
        author TEXT
        poem TEXT
        votes INT
    xcomment
        comment_id INT PRIMARY KEY AUTO_INCREMENT,
        author TEXT
        words TEXT
        sid BIGINT
"""
RESULT_FILE='c:\\projects\\ifa\\result.html'
HTML_PAGE="""<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
    <link href="ifastyles.css" rel="STYLESHEET" />
    <title>ifa contest</title>
</head>
<body>
<h1>Results</h1>
%s
</body>
</html>
"""
#-------------------------------------
# HTML fragments
HTML_ALL_SLOGANS_WRAPPER="""
<div>
%s
</div>
"""
HTML_SLOGAN_BLOCK_WRAPPER="""
<div id="slogan%s" class="one-slogan">
%s
</div>
"""
HTML_SLOGAN_BLOCK_SHOW="""
<fieldset>
        <legend>
        <span class="bigvotes">%s</span>
        <span class="smallvotes">stemmer</span>
        </legend>
        <div class="slogan">
                %s
         </div>
        <div class="slogan-author">
        %s
        </div>
        <div style="text-align:right">
                <input class="vote-button-text" 
                       type="submit" value="Stem p� denne"
                       onclick="handleVote('%s');return false;"/>
                <input class="comment-button-text" 
                       type="submit" value="Vis kommentarer"
                       onclick="expandComment('%s');return false;"/>
        </div>
        <!-- kommentarer -->
        <div id=comments%s>
        %s
        </div>
</fieldset>
"""
HTML_SLOGAN_BLOCK_HIDE="""
<fieldset>
        <legend>
        <span class="bigvotes">%s</span>
        <span class="smallvotes">stemmer</span>
        </legend>
        <div class="slogan">
                %s
         </div>
        <div class="slogan-author">
        %s
        </div>
        <div style="text-align:right">
                <input class="vote-button-text" 
                       type="submit" value="Stem p� denne"
                       onclick="handleVote('%s');return false;"/>
                <input class="button-text" 
                       type="submit" value="Skjul kommentarer"
                       onclick="collapseComment('%s');return false;"/>
        </div>
        <!-- kommentarer -->
        <div id=comments%s>
        %s
        </div>
</fieldset>
"""
HTML_MAKE_NEW="""
<a name="newslogan">
</a>
<div class="make-new-area" style="margin-top:40px">
    <fieldset>
    <legend><span class="new-slogan-header">Lag et nytt slagord</span></legend>
    <form action="#">
        <div class="new-slogan">
        <div>
            <textarea name="new_slogan" cols="60" rows="6"></textarea>
        </div>
        <div>
            Fra: <input type="text"  name="new_slogan_author" size="15"/>
            <input class="button-text" type="submit" value="Send inn"
                   onclick="handleNewSlogan(this.form);return false;"/>
        </div>
        </div>
    </form>
    </fieldset>
</div>
"""
HTML_COMMENT_BLOCK="""
<div class="comment">
<div class="comment-author">
  %s:
</div>
<div class="comment-words">
  %s
</div>
</div>
"""
HTML_COMMENT_BLOCK_WRAPPER="""
<div id="comments%s">
    <fieldset>
        <legend>Kommentarer</legend>
        %s
    </fieldset>
</div>
"""
HTML_NEW_COMMENT="""
<form action="#">
<div class="new-comment">
    <div>
        Din kommentar:<br/>
        Fra: <input type="text" name="new_comment_author" size="15"/>
    </div>
    <div>
        <textarea name="new_comment" cols="40" rows="2"></textarea>
        <input class="button-text" type="submit" value="Send inn"
               onclick="handleNewComment(this.form,'%s'); return false;"/>
    </div>
</div>
</form>
"""
#----------------------------------------
# SQL statements
SQL_ONE_SLOGAN="""SELECT * FROM xslogan WHERE slogan_id =%s;"""
SQL_UPDATE_SLOGAN_VOTES="""UPDATE xslogan SET votes=%d WHERE slogan_id=%s;"""
SQL_INSERT_SLOGAN="""INSERT INTO xslogan(slogan_id,author,poem,votes ) 
                     values('%s','%s','%s',1); """
SQL_ALL_SLOGANS="""SELECT * FROM xslogan ORDER BY votes DESC;"""
SQL_COMMENTS_FOR_SLOGAN="""SELECT * FROM xcomment WHERE sid=%s; """
SQL_INSERT_COMMENT="""INSERT INTO xcomment(sid,author,words) 
                      values(%s,'%s','%s');"""

#------------------------------------------------
# connect and execute a sql-request
#------------------------------------------------
def connectAndExecute(sql):
    try:
        conn=MySQLdb.connect(host='frigg.hiof.no',
                             user='student',
                             passwd='student',
                             db='bsdiverse')
        cursor=conn.cursor()
        cursor.execute(sql)
        result_tab=cursor.fetchall()
        conn.commit()
        cursor.close()
        conn.close()
        return result_tab
    except MySQLdb.Error, e:
        print "Error %d: %s" % (e.args[0], e.args[1])
        return None

#---------------------------------------------------
# Update the votes for one slogan
#---------------------------------------------------
def updateSloganVotes(sid):
    result=connectAndExecute(SQL_ONE_SLOGAN%sid)
    votes=int(result[0][3])+1
    result=connectAndExecute(SQL_UPDATE_SLOGAN_VOTES%(votes,sid))
#-----------------------------------------------------
# We have a comment for a slogan, insert it in comments
#------------------------------------------------
def insertComment(sid,comment,commentator):
    connectAndExecute(SQL_INSERT_COMMENT%\
         (sid,urllib.quote(commentator),urllib.quote(comment)))
#-----------------------------------------------------
# We have a new slogan, insert it with one vote
#------------------------------------------------
def insertSlogan(slogan,author):
    d=datetime.datetime.now()
    S='%04d%02d%02d%02d%02d%02d'%\
    (d.year,d.month,d.day,d.hour,d.minute,d.second)
    slogan=slogan.replace('\r\n','\n')
    slogan=slogan.replace('\n','<br/>')
    result=connectAndExecute(SQL_INSERT_SLOGAN%\
    (S,urllib.quote(author),urllib.quote(slogan)))
#--------------------------------------------------
# produce one slogan, with or without comments
# this block is without the outer <div id="sloganid">
# without: HTML_SLOGAN_BLOCK_WRAPPER
#--------------------------------------------------
def getOneSlogan(sloganId,commentWanted):
    row=connectAndExecute(SQL_ONE_SLOGAN%sloganId)
    if row==None:
        return  'Feil i data'
    resultTxt=''
    sid=str(row[0][0])
    author=urllib.unquote(str(row[0][1]))
    poem=urllib.unquote(str(row[0][2]))
    votes=str(row[0][3])
    comments=''
    if commentWanted:
        comments=getAllComments(sloganId)
        resultTxt=HTML_SLOGAN_BLOCK_HIDE%\
        (votes,poem,author,sid,sid,sid,comments)
    else:
        resultTxt=HTML_SLOGAN_BLOCK_SHOW%\
        (votes,poem,author,sid,sid,sid,comments)
    return resultTxt
#---------------------------------------------------
# produce a list of all slogan blocks
# Each idetified by the outer <div id="sloganid">
# HTML_SLOGAN_BLOCK_WRAPPER
#---------------------------------------------------
def getAllSlogans(commentedId):
    result=connectAndExecute(SQL_ALL_SLOGANS)
    if result==None:
        return 'Feil i data'
    resultTxt=''
    for row in result:
        sid=str(row[0])
        author=urllib.unquote(str(row[1]))
        poem=urllib.unquote(str(row[2]))
        votes=str(row[3])
        comments=''
        aSlogan=''
        if commentedId == sid:
            comments=getAllComments(commentedId)
            aSlogan=HTML_SLOGAN_BLOCK_HIDE%\
            (votes,poem,author,sid,sid,sid,comments)
        else:
            aSlogan=HTML_SLOGAN_BLOCK_SHOW%\
            (votes,poem,author,sid,sid,sid,comments)
        resultTxt+=HTML_SLOGAN_BLOCK_WRAPPER%(sid,aSlogan)
    resultTxt= HTML_ALL_SLOGANS_WRAPPER%resultTxt
    resultTxt+=HTML_MAKE_NEW
    return resultTxt
#---------------------------------------------------
# produce a list of all comments for a slogan
# including the identifying outer div
# HTML_COMMENT_BLOCK_WRAPPER
#---------------------------------------------------
def getAllComments(sid):
    result=connectAndExecute(SQL_COMMENTS_FOR_SLOGAN%sid)
    if result==None:
        resultTxt=HTML_NEW_COMMENT%sid
        return resultTxt
    resultTxt=''
    if len(result)==0:
        resultTxt='Ingen komentarer'
    else:
        for row in result:
            author=urllib.unquote(str(row[1]))
            com=urllib.unquote(str(row[2]))
            aComment=HTML_COMMENT_BLOCK%(author,com)
            resultTxt+=aComment
    resultTxt+=HTML_NEW_COMMENT%sid
    resultTxt= HTML_COMMENT_BLOCK_WRAPPER%(sid,resultTxt)
    return resultTxt
#-------------------------------------------------
# what is the job ?
#-------------------------------------------------
form=cgi.FieldStorage()
print 'Content-type: text/html\n'
RESULT_TXT=''
if form.has_key('what'):
    what=form['what'].value
    if what=='allslogans':
        RESULT_TXT=getAllSlogans(-1)
    elif what=='newslogan':
        slogan='slogan'
        author='author'
        if form.has_key('slogan'):
            slogan=form['slogan'].value
        if form.has_key('author'):
            author=form['author'].value
        insertSlogan(slogan,author)
        RESULT_TXT=getAllSlogans(-1)
    elif what=='vote':
        sid=''
        if form.has_key('slogan'):
            sid=str(form['slogan'].value)
        updateSloganVotes(sid)
        RESULT_TXT=getAllSlogans('-1')
    elif what=='expand':
        sid=''
        if form.has_key('slogan'):
            sid=str(form['slogan'].value)
        RESULT_TXT=getOneSlogan(sid,True)
    elif what=='collapse':
        sid=''
        if form.has_key('slogan'):
            sid=str(form['slogan'].value)
        RESULT_TXT=getOneSlogan(sid,False)
    elif what=='newcomment':
        comment='comment'
        author='author'
        sid=''
        if form.has_key('comment'):
            comment=form['comment'].value
        if form.has_key('author'):
            author=form['author'].value
        if form.has_key('slogan'):
            sid=str(form['slogan'].value)
        insertComment(sid,comment,author)
        RESULT_TXT=getOneSlogan(sid,True)
    else:
        RESULT_TXT='ikke implementert'
else:
    RESULT_TXT='vet ikke hva'
print RESULT_TXT

Merk følgende:

  • Skriptet produserer HTML-fragmenter som inneholder kode som i sin tur initierer nye forespørsler når de klikkes. Se f.eks. variabelen: HTML_SLOGAN_BLOCK_SHOW.
  • Det er et problem med tegnkoding og databasen. Problemet er "korsluttet" ved å bruke urllib.quote() når data skal legges inn og urllib.unquote() når data hentes ut. Dette fører til at de dataene som ligger i databasen har erstattet alle ikke-asci tegn med %nn formatet, nn=2-sifret hexadesimalt tall. Ikke den vakreste av løsninger, men det virker.
Referanser

Koden er referert i teksten

Se modulen: AJAX

Vedlikehold

B. Stenseth, august 2006

( Velkommen ) Eksempler >IFA ( Data om land )