Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use functions on client side outside events #140

Open
joaoventura opened this issue Jan 29, 2014 · 7 comments
Open

Use functions on client side outside events #140

joaoventura opened this issue Jan 29, 2014 · 7 comments

Comments

@joaoventura
Copy link
Contributor

Suppose I want to implement a function on the client side, which is not specifically triggered by an event. Something like this:

class HellowWorldApp:
    def initialize(self, **kwargs):
        self.rdoc = kwargs['remote_document']
        self.rdoc.body.element(h1='Hello World!!!').events.add(click=self.clicked, translate=True)

    def clicked(self):
        self.heavy_clientside_computation()

    def heavy_clientside_computation(self): 
        # Do something heavy on the client's browser

So, by clicking on "Hello World" the "clicked" event is fired, but then the browser throws a JS Error because "heavy_clientside_computation(self)" was not compiled to Javascript and sent to the client..

Is there a solution for this now as the framework is?

If not, may I suggest something like a decorator so the framework knows which functions are to be sent to the client? It could be something like the following, and it could be applied to all "remote" methods, including those triggered by events (as python zen's explicit is better than implicit):

class HellowWorldApp:
    def initialize(self, **kwargs):
        self.rdoc = kwargs['remote_document']
        self.rdoc.body.element(h1='Hello World!!!').events.add(click=self.clicked, translate=True)

    @remotemethod
    def clicked(self):
        self.heavy_clientside_computation()

    @remotemethod        
    def heavy_clientside_computation(self): 
        # Do something heavy on the client's browser

In this case, all function calls inside @remotemethod's would be local browser calls, unless there was the rpc() call, which would be targeting the server.

@skariel
Copy link
Owner

skariel commented Jan 29, 2014

You have to translate heavy_clientside_computation like this:

class HellowWorldApp:
    def initialize(self, **kwargs):
        self.rdoc = kwargs['remote_document']
        self.rdoc.body.element(h1='Hello World!!!').events.add(click=self.clicked, translate=True)
        self.rdoc.translate(self.heavy_clientside_computation)

    def clicked(self):
        heavy_clientside_computation()

    def heavy_clientside_computation(self): 
        # Do something heavy on the client's browser

see below the hello world example, I modified it so every click on the h1 also prints to the console. I've tested it works as expected:

from webalchemy import server

class HellowWorldApp:
    def initialize(self, **kwargs):
        self.rdoc = kwargs['remote_document']
        self.rdoc.translate(self.print_something)
        self.rdoc.body.element(h1='Hello World!!!').events.add(click=self.clicked, translate=True)
        self.rdoc.body.element(h2='--------------')
        self.rdoc.stylesheet.rule('h1').style(
            color='#FF0000',
            marginLeft='75px',
            marginTop='75px',
            background='#00FF00'
        )

    def print_something(self):
        print('hi there!')

    def clicked(self):
        self.textContent = self.textContent[1:]
        rpc(self.handle_click_on_backend, 'some message', 'just so you see how to pass paramaters')
        print_something()

    def handle_click_on_backend(self, sender_id, m1, m2):
        self.rdoc.body.element(h1=m1+m2)

if __name__ == '__main__':
    # this import is necessary because of the live editing. Everything else works OK without it
    from hello_world_example import HellowWorldApp
    server.run(HellowWorldApp)

I think the decorator is a good idea but I need to think how this could be implemented, since currently it needs the runtime remote-document obejct to work...

EDIT: no self. needed to call heavy_clientside_computation

@joaoventura
Copy link
Contributor Author

I also saw that "problem" with the remote-document object.
The only solution I could find so far was to make a base class for the framework (something like WebAlchemyApp - which everyone's apps would inherit from), and make the remote-document a property of that base class.
Then, the decorator would only be a wrapper to "self.rdoc.translate(func)"..

What do you think? Any drawbacks by having a WebAlchemyApp Base Class? This would allow to simplify things such as this one..

@skariel
Copy link
Owner

skariel commented Jan 29, 2014

I'm not sure how this would work... Decorating happens at "compile" time right? I'm ok with having to inherit, could you open a pull request on a different branch so I can test, Or just put the needed code in this thread? Otherwise you'll have to explain in greater detail how it will work so I'll understand and implement

Thanks!

@skariel
Copy link
Owner

skariel commented Jan 29, 2014

Maybe one way to make this work is having a decorator that just "marks" which functions should be translated. Then when the app is created the server could scan the app and translate the required functions

@joaoventura
Copy link
Contributor Author

I'm also not very used to decorators, but I'll try to get something to work.

Your second idea (translation on-the-fly) is also good since it would allow to do another thing that I was going to suggest later: Apply the same thing (remotemethods vs local methods) on generic classes. This could be useful for API's where you would like much of the code of a class to be executed on the client, and some code on the server (things like DB accesses).

Things like the following would be great to do transparently:

class SomeAPI:

    def db(client, op):
        """ This opens a connection on the local drive and executes an operation. """"
        conn = open_connection("home/user/path/somedb.db")
        if (op == "save"):
            conn.save(client)
        elif ......

    @remotemethod
    def saveClient(self, client):
        self.db(client, "save")

The remote method would "see" that the function "db" was not local, and would in the background do an RPC call to execute it on server...

To put code on the client allows us, developers, to have less server requirements, that is, we can use cheaper servers.. :)

@Iftahh
Copy link
Collaborator

Iftahh commented Jan 29, 2014

Just a small note: I think a better name for the decorator would be
@clientside.

remote method is an unclear name, If you have server centric point of
view you would read remote as client side and if you have browser centric
point of view you would read remote as server side.

On Wednesday, January 29, 2014, João Ventura [email protected]
wrote:

I'm also not very used to decorators, but I'll try to get something to
work.

Your second idea (translation on-the-fly) is also good since it would
allow to do another thing that I was going to suggest later: Apply the same
thing (remotemethods vs local methods) on generic classes. This could be
useful for API's where you would like much of the code of a class to be
executed on the client, and some code on the server (things like DB
accesses).

Things like the following would be great to do transparently:

class SomeAPI:

def db(client, op):
    """ This opens a connection on the local drive and executes an operation. """"
    conn = open_connection("home/user/path/somedb.db")
    if (op == "save"):
        conn.save(client)
    elif ......

@remotemethod
def saveClient(self, client):
    self.db(client, "save")

The remote method would "see" that the function "db" was not local, and
would in the background do an RPC call to execute it on server...

To put code on the client allows us, developers, to have less server
requirements, that is, we can use cheaper servers.. :)

Reply to this email directly or view it on GitHubhttps://github.com//issues/140#issuecomment-33619111
.

@skariel
Copy link
Owner

skariel commented Feb 6, 2014

just tried so close this issue, but everything I try has some major downside. For now I think it's better to just continue with what we have.

Also- the way I showed above for translation is dangerous. It can create namespace problems. For e.g. when using translate('print_hi') it will create a function named print_hi in the global js namespace. Imagine you translate click- you would have 10 versions.

The solution is to use self.print_hi = translate('self.print_hi') and then call this function from JS with srv: srv(self.print_hi)(). This works since the proxy has a mangled name, and the srv escapes that name.

So the correct hello world above should look like:

from webalchemy import server
from webalchemy.remotedocument import rpc, srv

class HellowWorldApp:
    def initialize(self, **kwargs):
        self.rdoc = kwargs['remote_document']
        self.print_hi = self.rdoc.translate(self.print_hi)
        self.rdoc.body.element(h1='Hello World!').events.add(click=self.clicked, translate=True)
        self.rdoc.body.element(h2='--------------')
        self.rdoc.stylesheet.rule('h1').style(
            color='#FF0000',
            marginLeft='75px',
            marginTop='75px',
            background='#00FF00'
        )

    def clicked(self):
        self.textContent = self.textContent[1:]
        rpc(self.handle_click_on_backend, 'some message', 'just so you see how to pass paramaters')
        srv(self.print_hi)()

    def print_hi(self):
        print('hi!')

    def handle_click_on_backend(self, sender_id, m1, m2):
        self.rdoc.body.element(h1=m1+m2)

This version works, I just tested. (note the imported srv and rpc functions above are just empty methods so the IDE doesn;t complain, I still haven't syncd this with github...)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants