Near the perfect ajax method #1
Home Home Contact us Contact
home News Projects Tests Files Follow manatlan on Twitter Sign in
back 2008-11-08 14:26:27

Near the perfect ajax method #1

It's not my first try. I like to find the ultimate way to do this kind of thing. In this approach, I really needed a way to send multiple needs, and to retrieve many results. It's based on jQuery (clientside) and webpy (serverside) (with a json lib like demjson), as always. It's a proof of concept, but it feeds my needs (and perhaps yours)

On the client side, it's a classical ajax method :

function ajax( method, data, callback ) {...}

method define the server side method, data is a classical dict, and callback is a optional callback.

A simple example, on client side, could be :

<button onclick='ajax( "method1", {arg1:"hello"} );'>test</button>

On server side :

@Ajax.entry
def method1(arg1="bonjour"):
    return {"result":arg1*2}

Which does nothing, except returning a object which have got an attribut result which contains 'hellohello'. But the previous client side call don't care about the result. To use the result on the client side, we should use something like this :

<button onclick='ajax( "method1", {arg1:"hello"}, function(o) {alert(o.result);} );'>test</button>

Which should display 'hellohello' in an alert box.

Note, as on the server side, the python method method1 has got a default value for param arg1. It could be possible to call the ajax method without providing a arg1 :

<button onclick='ajax( "method1", {}, function(o) {alert(o.result);} );'>test</button>

Which should display 'bonjourbonjour' in an alert box.

Until here, it's a very classical ajax method : A call, and a return as a dict. A neat feature is that you can fill html tags on server side. On this simple example, client side :

<button onclick='ajax( "method2" );'>test</button>
<div id="out1"></result>
<div id="out2"></result>

server side :

@Ajax.entry
def method2():
    return {"[#out1]":"hello1","[#out2]":"hello2"}

The div#out1 and div#out2 will be automatically filled with the response of the server method. It use the "[]" trick to encapsulate a jquery selector. It's just a simple way, to feed some tag on the clientside. Another neat feature is that you can send back a javascript call in your response, by adding a key script in your result like this :

@Ajax.entry
def method2():
    return {"[#out1]":"hello1","[#out2]":"hello2","script":"alert(1)"}

which should feed div#out1 and div#out2, and display an alert box containing '1'. So it's easy to define javascript calls, or feed a client tag, by doing it on the serverside, in a python way.

But the great feature, is that you can call many methods from the server side in one client ajax call, by passing a list of methods, like this :

<button onclick='ajax( ["method1","method2"], {arg1:'ciao'});'>test</button>

method1 will be executed at first, and method2 at last. Each method will try to find its own parameters in the dict. Thus, parameters can be shared between methods.

And better : method1 could set parameters for method2 like this:

@Ajax.entry
def method1(arg1):
    return {"arg2":arg1*2}

@Ajax.entry
def method2(arg2):
    return {"[#out]":arg2*2}

arg2 is populated by method1, thus method2 can use it (although it was not defined at the ajax call). It should set 'ciaociaociaociao' in a div#out.

Here is the webpy(v0.3) code :

URLS = (
    '/',"Ajax",
    '/ajax',"Ajax",
)

import inspect
import demjson

class Ajax:
    """
        function ajax(methods,dico,callback)
        {
            if(!dico) dico={};
            dico["_methods_"] = methods;

            $.post("/ajax", dico,
                function(data){
                    var obj = eval("("+data+")");
                    if(obj._err_)
                        alert(obj._err_);
                    else {
                        if(obj._contents_) {
                            for(var idx in obj._contents_) {
                                var c_c=obj._contents_[idx];
                                $(c_c[0]).html(c_c[1]);
                            }
                        }

                        if(obj._scripts_) {
                            for(var idx in obj._scripts_)
                                eval(obj._scripts_[idx]);
                        }

                        if(callback)
                            callback(obj)
                    }
                }
            );
        }
    """
    class _MethodDict_(dict):
      def __call__(self, func):
        self[func.__name__] = func
    entry = _MethodDict_()  #decorator


    def POST(self):
        def log(a):
            print str(a)
        def ResultSet(dico):
            return demjson.encode(dico)
        def error(m):
            log("--> "+err)
            return ResultSet({"_err_":str(m)})

        data=web.input(_methods_=[])
        methods = data["_methods_"]
        del data["_methods_"]

        result = {}
        for method in methods:
            if method in Ajax.entry:
                fct=Ajax.entry[method]
            else:
                return error("Method '%s' doesn't exist"%(method,))
            fct_args=inspect.getargspec(fct)[0]
            args={}
            for a in fct_args:
                if a in data: # else, hope there is a default value in method def
                    args[a] = data[a]   #fill args for method
            log("ajax call "+method+"("+str(args)+")")
            try:
                rs = fct(**args)
                log("--> "+str(rs))
                if rs:
                    if type(rs)!=dict:
                        return error("Method '%s' don't return a dict"%(method,))
                    else:
                        for k in rs.keys():
                            if k[0]=="[" and k[-1]=="]":
                                result.setdefault("_contents_",[]).append( [k[1:-1],rs[k]] )
                                del rs[k]

                        if "script" in rs:
                            result.setdefault("_scripts_",[]).append(rs["script"])
                            del rs["script"]

                        result.update( rs )
                        data.update( rs )
            except Exception,m:
                err="Error in '%s' : %s : %s"%(method,Exception,m)
                return error(err)

        log("Return:"+str(result))
        return ResultSet(result)

    def GET(self):
        web.header("content-type","text/html")
        return """
        <html>
        <head>
            <script type="text/javascript" src="/static/jquery.js"></script>
            <script type="text/javascript">%s</script>
        </head>
        <body>
            <button onclick='ajax( "jack", {arg2:"hello1"} );'>go1</button>
            <button onclick='ajax( "jo", {arg1:"hello2"} );'>go2</button>
            <button onclick='ajax( "jo" );'>go3</button>
            <button onclick='ajax( ["jo","jack"], {} );'>go4</button>
            <button onclick='ajax( ["jo","jack"], {arg1:"hello5"} );'>go5</button>
            <div id="result"></div>
        </body>
        </html>
        """ % Ajax.__doc__


@Ajax.entry
def jo(arg1="koko"):
    return {"arg2":arg1,"script":"$('#result').css('border','1px solid red')"}

@Ajax.entry
def jack(arg2):
    return {"[#result]":arg2*2}

RSS Python Powered Get Ubuntu
©opyleft 2008-2017 - manatlan