LOAD, javascript and a loading gif

1,561 views
Skip to first unread message

Liam

unread,
Jan 9, 2012, 1:58:30 PM1/9/12
to web...@googlegroups.com
Hi all,

I'm currently writing a plugin for part of a bigger project that can take some time doing processing server side and I'm trying to make it as modular as possible; thus the plugins and components. The problem I'm trying to solve is this: The application uses LOAD to embed some html generated by the plugin in the page. A link in the LOADed html will start some analyses and will be replaced by an image of a loading gif while waiting for the analyses to complete and the html to be returned.

I've been trying to return javascript with the html from the plugin that activates when the link is clicked without any success. I don't even know if the javascript will be applied to the link in the html that it is returned with. I've tried a few approaches:
1. Script elements seem to disappear if placed inside the view/controller.
2. response.files.append(...) doesn't add a js script to the outer page's files.
3. _onclick="..." in the A helper gets overwritted by the web2py_component function that allows the plugin html and responses to be loaded inside the page
I've done a day's digging, but turned up nothing.
4. I've also tried setting response.js in the component controller, but I'm having trouble debugging this because nothing turns up in FireBug.

Does someone have any ideas? It doesn't need to deal directly with javascript, but it does need to be a component plugin.

Cheers,
Liam

David

unread,
Jan 9, 2012, 2:28:43 PM1/9/12
to web...@googlegroups.com
There is a loading gif already built into web2py.

Look at the parameters of the LOAD function it was added almost a year ago.

Anthony

unread,
Jan 9, 2012, 2:57:34 PM1/9/12
to web...@googlegroups.com
On Monday, January 9, 2012 1:58:30 PM UTC-5, Liam wrote:
Hi all,

I'm currently writing a plugin for part of a bigger project that can take some time doing processing server side and I'm trying to make it as modular as possible; thus the plugins and components. The problem I'm trying to solve is this: The application uses LOAD to embed some html generated by the plugin in the page. A link in the LOADed html will start some analyses and will be replaced by an image of a loading gif while waiting for the analyses to complete and the html to be returned.

If you want to click a link in the component and have the contents of the component replaced with the response from that link, you can use the A() helper with the "cid" argument, as described here: http://web2py.com/books/default/chapter/29/12#Trapped-Ajax-links.

You would then have to add some Javascript code to display the "loading" gif while waiting.
 

I've been trying to return javascript with the html from the plugin that activates when the link is clicked without any success. I don't even know if the javascript will be applied to the link in the html that it is returned with. I've tried a few approaches:
1. Script elements seem to disappear if placed inside the view/controller.

Not sure what you mean by that -- can you show some code?
 
2. response.files.append(...) doesn't add a js script to the outer page's files.

Right. response.files generally won't do anything when a component loads (unless the component view includes code that calls response.include_files(), which is generally not the case, and would be tricky for CSS files in particular, which must be loaded in the document head).

Anthony

Anthony

unread,
Jan 9, 2012, 2:59:04 PM1/9/12
to web...@googlegroups.com
On Monday, January 9, 2012 2:28:43 PM UTC-5, David J wrote:
There is a loading gif already built into web2py.

Look at the parameters of the LOAD function it was added almost a year ago.

I believe that only shows up on the initial display of the full page that contains the component, not on subsequent reloads of the component within the page.

Anthony 

Niphlod

unread,
Jan 9, 2012, 5:15:33 PM1/9/12
to web2py-users
uhm, don't know if it'll help you, BTW, I also had to figure a "way"
to display some "loading pictures" (from now on, I'll call them
"spinners") to my pages.
I had to face 2 distinct problems:
- loading components via Ajax (through LOAD() or web2py_component())
- prevent FOUC on very complex pages

for the first problem I actually "hacked" web2py.js (web2py_ajax.js or
so).
the ajax function needs to be modified....
the basic idea is to replace content of the target with a spinner (in
this case, a div containing the message "loading...") before sending
the request, and upon success replace it with the content actually
loaded...
you can add a beforeSend parameter for that and take care of replacing
the content adding something to the success function.

My code actually is something like this...

function ajax(u,s,t) {
query = '';
if (typeof s == "string") {
d = jQuery(s).serialize();
if(d){ query = d; }
} else {
pcs = [];
if (s != null && s != undefined) for(i=0; i<s.length; i++) {
q = jQuery("[name="+s[i]+"]").serialize();
if(q){pcs.push(q);}
}
if (pcs.length>0){query = pcs.join("&");}
}
jQuery.ajax({
type: "POST",
url: u,
data: query,
beforeSend: function( xhr ) {
if(t) {jQuery("#" + t).html('<div
id="loadingwindow">Loading...</div>')};
},
success: function(msg) {
if(t) {
if(t==':eval') eval(msg);
else {
jQuery("#loadingwindow").fadeOut(400, function
() {
jQuery("#" + t).html(msg).hide().fadeIn(400);
});
}
}
}
});
}

for the second problem, I had to hide window contents because of some
complex javascript needs to be run to validate several fields and to
rearrange heavily the DOM (tabs, selects with a large number of
options to turn into multiselects, a tree with jstree) until the
"transformation" completes.
Fortunately I could figure out what was the "script" block that is
going to be always the last to be finished (not executed), so I can
address that "event" with the "action" of displaying my page again.
All this introduction to warn you about an hiccup in the following
method: if you have built (like me) some widgets to be modular, you'll
end up having various <script> blocks around your page: from what I
can tell you can only be sure that they are executed "in order" of
what is found in the page, but you can't tell what will be the last to
stop executing. NB: this method works only if you must display a
spinner in a one-time-only manner, e.g.: you have a complex page that
needs to be temporarily "hidden" and you want to prevent FOUC.

Truth told, I figured out this way to prevent FOUC and display the
page after all DOM work is done :

step 1:
add to the css this rule

#wloading.amloading {
position: absolute;
left: -10000px;
}

step 2: add a function to web2py.js (or anywhere else that you are
sure it's loaded with every page)

function finished_loading() {
if ($('loadingwindow').length == 0) {
setTimeout(function() {
$("#wloading").removeClass("amloading");
$("#loadingwindow").remove();
}, 1500);
}
}

step 3:
define two "widgets" in models (can be elaborated further with
positioning, simulating "modal" loading, etc etc etc.). Following is
the basic ones

def loading_widget_start():
script = """
$("#wloading").addClass("amloading");
$("#wloading").before('<div id="loadingwindow">Loading...</div>');
"""
rtn = SCRIPT(script, _type="text/javascript")
return rtn

def loading_widget_stop():
script = """
setTimeout(function() {
finished_loading();
}, 5000);
"""
rtn = SCRIPT(script, _type="text/javascript")
return rtn

step 4:
embed every page that needs to be loaded in a <div id="wloading">

step 5:
add {{=loading_widget_start()}} right after <div id="wloading"> and
before the actual content

step 6:
put {{=loading_widget_stop()}} at the very end of the page (just to be
sure)

You can now use the finished_loading() "callback" after the most
expensive <script> or complex functions and be sure that the content
will be hidden until callback is called. If you don't call
finished_loading() explicitely, the loading_widget_stop() will be
called at the very end of your page and will wait 5 seconds before
showing the page to the user.

If you have further questions don't be afraid to ask.

PS: I'm aware that I'm not using a document.ready event to fire all
up, that kind of "blocks" the whole idea of using a function before
all DOM is loaded.....from what I can tell as long as jquery is loaded
in the <head> you can be sure that the library is loaded before the
body, so working on a div that is declared just before the "managing
it with jquery" <script> will work.

I'm not a javascript guru, I'm just compelled to have a page working
the way I (or my users) want in the shortest time possible.

If you know a better and "traditional" method please tell me, I'm
eager to learn and maybe you'll help others in the same "condition" :D

Anthony

unread,
Jan 9, 2012, 5:51:27 PM1/9/12
to web...@googlegroups.com
for the first problem I actually "hacked" web2py.js (web2py_ajax.js or
so).
the ajax function needs to be modified....
the basic idea is to replace content of the target with a spinner (in
this case, a div containing the message "loading...") before sending
the request, and upon success replace it with the content actually
loaded...

Note, if there are no other Ajax requests happening on the page other than the one(s) for which you want to trigger a loading message, you can use the jQuery .ajaxStart() and .ajaxStop() event handlers to show and hide the message, rather than hacking web2py.js.

Anthony 

Niphlod

unread,
Jan 9, 2012, 6:30:49 PM1/9/12
to web2py-users
I don't recall the exact one happened to be my case, but plenty of
jquery plugins use ajax, one was working oddly (maybe a js master
could see if that plugin was bugged, but I'm not): I didn't want to
mess with that one and those following that, so I hacked web2py.js
(didn't think much about that, the functionality is fully preserved
and there are 3-4 additional lines in total, not a big "hack"
anyway...)

Liam

unread,
Jan 10, 2012, 5:37:51 AM1/10/12
to web...@googlegroups.com
I got it working towards the end of this post. I've included to original text because I thinks it explains my problem better. Solution is in red.

I obviously didn't state my problem clearly. The issue isn't replacing all of the loaded html with an image, just the link that gets clicked. A more specific question related to the way I tried to solve it is: How do I get javascript in a component plugin to execute each time after the component is loaded so the javascript is applied to the loaded component elements? The javascript belongs in the plugin and can not be put in the application, this would break the modularity.

Hopefully some code will clear things up.

plugin_test controller:
def overview():
    if len(request.args) > 0 and request.args[0] == 'refresh'
        time.sleep(4) # In place of the plugin's data processing code
    grid = SQLFORM.grid(db.plugin_test_tablename)
    refresh = DIV(A('Refresh', _class='button replacewloading', _href=URL(c='plugin_test', f='overview.load', args=['refresh']), cid=request.cid))
    return dict(grid=grid, refresh=refresh)

plugin_test view:
{{=grid}}
{{=refresh}}

application view:
{{extend 'layout.html'}}
{{=LOAD('plugin_test', 'overview.load', ajax=True)}}

the javascript I'm trying to execute:
$(function() {
    $(".replacewloading").click(function() {
        $(this).parent().html('<span><img src="static/plugin_test/images/loading.gif"/>Refreshing...</span>')
    } );
} );

So far I've tried:
  1. Creating a javascript file in the plugin static directory called loading.js and writing "response.files.append(URL('static', 'plugin_test/js/loading.js'))" in the plugin controller. If it worked, this would nevertheless be a poor solution as the image isn't configurable.
  2. Removing all line breaks from the script and writing "response.js(script)" in the plugin controller. Again no luck.
  3. Including the script directly into the view html via the view and controller. This is the case where the script seems to disappear. When I inspect the html using Firebug, there's no trace of it. It also leaves open the question of how it will get executed when the component is finished loading. It turns out that this is the working solution. The script doesn't disappear, it can be found in jquery.js/eval/seq. I'm not knowledgable about javascript/jquery so I have no idea where that actually is. It isn't part of the filesystem. Some sort of virtual client-side storage perhaps? I'm still not sure why it wasn't working earlier, perhaps I made a syntax error somewhere.

The refresh button should get replaced while the page reloads so the user can't click it again, and they know that something is still happening in the background.

Sorry for the confusion and thanks for the help from all of you,
Liam

Final solution for those who are interested:

plugin_test controller:
def overview():
    from gluon.tools import PluginManager
    plugins = PluginManager('test', loading_gif=URL('static', 'plugin_test/images/loading.gif'))

    if len(request.args) > 0 and request.args[0] == 'refresh'
        time.sleep(4) # In place of the plugin's data processing code
    grid = SQLFORM.grid(db.plugin_test_tablename)
    refresh = DIV(A('Refresh', _class='button replacewloading', _href=URL(c='plugin_test', f='overview.load', args=['refresh']), cid=request.cid))
    return dict(src=plugins.test.loading_gif, grid=grid, refresh=refresh)

plugin_test view:
<script>
$(function() {

    $(".replacewloading").click(function() {
        $(this).parent().html('<span><img src="{{=src}}"/>Refreshing...</span>')
    } );
} );
</script>
{{=grid}}
{{=refresh}}

application view:
{{extend 'layout.html'}}
{{=LOAD('plugin_test', 'overview.load', ajax=True)}}

kenji4569

unread,
Jan 10, 2012, 8:12:30 AM1/10/12
to web2py-users
>How do I get javascript in a component plugin to execute each time after the component >is loaded so the javascript is applied to the loaded component elements?

I'd encountered the similar problem and discussed before:

http://groups.google.com/group/web2py-developers/browse_thread/thread/fe66558085294456/210e0c8a2e007ecc?lnk=gst&q=plugins#210e0c8a2e007ecc

Although in this case, the above final solution would be good enough,
could you have a look my solution mentioned in the discussion?


Besides, jquery.spinner.js might be a good option for a preloader:

http://www.jqueryin.com/projects/spinner-jquery-preloader-plugin/#demos

I extensively use it for my projects (ex: http://dev.s-cubism.com/plugin_jstree).


Kenji
>    1. Creating a javascript file in the plugin static directory called
>    loading.js and writing "response.files.append(URL('static',
>    'plugin_test/js/loading.js'))" in the plugin controller. If it worked,
>    this would nevertheless be a poor solution as the image isn't configurable.
>    2. Removing all line breaks from the script and writing "
>    response.js(script)" in the plugin controller. Again no luck.
>    3. Including the script directly into the view html via the view and

Anthony

unread,
Jan 10, 2012, 3:06:16 PM1/10/12
to web...@googlegroups.com
  1. Removing all line breaks from the script and writing "response.js(script)" in the plugin controller. Again no luck.
Actually, this should work, but the syntax is:

response.js = '''<script>$(function() {$(".replacewloading").click(function() {$(this).parent().html('<span><img src="static/plugin_test/images/loading.gif"/>Refreshing...</span>')} );} );</script>'''

  1. Including the script directly into the view html via the view and controller. This is the case where the script seems to disappear. When I inspect the html using Firebug, there's no trace of it. It also leaves open the question of how it will get executed when the component is finished loading. It turns out that this is the working solution. The script doesn't disappear, it can be found in jquery.js/eval/seq. I'm not knowledgable about javascript/jquery so I have no idea where that actually is. It isn't part of the filesystem. Some sort of virtual client-side storage perhaps? I'm still not sure why it wasn't working earlier, perhaps I made a syntax error somewhere.
As far as I understand, if you insert a <script> tag into the DOM, it won't execute. However, when jQuery receives an html response via ajax, it automatically handles the execution of any script tags in the html, though I guess you won't see the script itself in the DOM where you expect it.

Anthony 

Liam

unread,
Jan 11, 2012, 3:53:24 AM1/11/12
to web...@googlegroups.com
Sorry, that was a typo.

Even something as simple as response.js = 'alert("Here");' or response.js = '<script>alert("Here");</script>' doesn't seem to work, although it does if included in the view.

Anthony

unread,
Jan 11, 2012, 9:11:29 AM1/11/12
to web...@googlegroups.com
On Wednesday, January 11, 2012 3:53:24 AM UTC-5, Liam wrote:
Sorry, that was a typo.

Even something as simple as response.js = 'alert("Here");' or response.js = '<script>alert("Here");</script>' doesn't seem to work, although it does if included in the view.

Can you show the code you tried? I tried the exact code you posted and simply added this to the overview() function:

response.js = '''<script>$(function() {$(".replacewloading").click(function() {$(this).parent().html('<span><img src="static/plugin_test/images/loading.gif"/>Refreshing...</span>')} );} );</script>'''

and it worked fine for me.

Anthony 

Liam

unread,
Jan 11, 2012, 12:02:29 PM1/11/12
to web...@googlegroups.com
web2py 1.99.4

plugin controller:
def overview():
    if len(request.args) > 0 and request.args[0] == 'refresh':
        import time
        time.sleep(4)
    body = H2('Component header')

    refresh = DIV(A('Refresh', _class='button replacewloading', _href=URL(c='plugin_test', f='overview.load', args=['refresh']), cid=request.cid))
   
    request.js = '''<script>$(function() {$(".replacewloading").click(function() {$(this).parent().html('<span><img src="static/plugin_test/images/loading.gif"/>Refreshing...</span>')} );} );</script>'''
   
    return dict(body=body, refresh=refresh)

plugin view:
{{=body}}

Anthony

unread,
Jan 11, 2012, 12:18:57 PM1/11/12
to web...@googlegroups.com
Two errors. I forgot to remove the <script> tag -- it isn't needed in response.js because the returned JS is eval'ed. Also, in your code, you have request.js, but it should be response.js, so try:

response.js = '''$(function() {$(".replacewloading").click(function() {$(this).parent().html('<span><img src="static/plugin_test/images/loading.gif"/>Refreshing...</span>')} );} );'''

Anthony

Liam

unread,
Jan 12, 2012, 12:25:55 PM1/12/12
to web2py-users
So I feel a little sheepish now. Making those two changes gives me the
second solution to the problem.
Reply all
Reply to author
Forward
0 new messages