Smalltalk Seaside - jQuery Ajax Callback
Asked Answered
I

2

7

So I have a non-Ajax callback working fine using this code (the 'convert' method calculates a new value for the 'result' instance variable):

        html form: [
        html text: 'Number to convert: '.
        html textInput
            callback: [ :value | self setNumtoconvert: value ];
            value: numtoconvert.
        html break.
        html text: 'Result: '.
        html text: result.
        html break.
        html submitButton
            value: 'Convert';
            callback: [ self convert ]
    ].

...and now I'm trying to 'Ajax-ify' it using jQuery. I've been trying something along these lines:

(html button)
    onClick: ((html jQuery ajax)
        callback: [ self convert]);
    id: 'calclink';
    with: 'Convert'.

...which isn't working, since I'm obviously missing some secret sauce. Can a Seaside expert chime in and give me a quick tutorial on converting 'regular' callback code to 'jQuery Ajax' callback code?

UPDATE I'm very close to figuring this out; after scouring the Web and re-reviewing the draft chapters in the Seaside book, I've changed my Ajax-ified button to this:

(html button)
    onClick: ((html jQuery ajax)
        callback:[:val | self setNumtoconvert: val.
            self convert.
            Transcript show: self getResult.]
            value:(html jQuery: '#txtNumToConvert') value;
        onComplete: ((html jQuery: '#txtResult') value: self getResult)
        );
    id: 'calclink';
    with: 'Convert'.

Now the only question is how to set the value of the '#txtResult' textbox; Transcript show: self getResult is displaying the correct value, but I can't get the 'onComplete' line to update the '#txtResult' value. I can use onComplete: ((html jQuery: '#txtResult') value: 'hello there') to insert a string constant in the textbox, but self getResult isn't providing a value for the textbox, although, again, I get the correct value when displaying self getResult in the transcript window.

UPDATE TWO My 'onComplete' line does work, but only after pressing the button (to calculate the value), refreshing the page, then pressing the button again (to display the calculated value in the 'txtResult' textbox. How do I fix this?

MY ANSWER

I finally came up with a solution that is easier to understand (for me) and less verbose that I really like:

renderContentOn: html
html form:[     
    html textInput
       id: #txtNumToConvert;
       callback: [ :value | self setNumtoconvert: value ];
       value: numtoconvert.
    html break.
    html span
       id: #result;
       with: result.
  html break.
  html anchor
     url: 'javascript:void(0)';
     onClick: ((html jQuery id: #result) load
       serializeForm;
       html: [self convert. html span with: result]);
     with: 'Convert to Decimal'.
 ]
Indiscipline answered 20/4, 2015 at 19:22 Comment(2)
Can you describe a bit more what is 'not working' ?Pena
Well, the 'result' textbox is not being updated with the result of assigning a new value to the 'result' class instance variable. When I click on the 'jQuery button', I see an Ajax call being triggered (using Firebug), but nothing appears to actually be happening....Indiscipline
P
5

First, you need to trigger the callbacks for the fields inside the form as well. The following code attaches a click-event handler to the button that performs an ajax request that will serialize the entire form and then execute the callback of the button.

(html button)
   onClick: ((html jQuery ajax)
       serializeForm;
       callback: [ self convert ]);
   id: 'calclink';
   with: 'Convert'.

When you use regular form submission, Seaside will trigger the callbacks for all fields inside the form for you. When you want to trigger the form submission as an ajax request, the Seaside-jQuery's #serializeForm method will also serialize the contents of all input fields inside the form and trigger their callbacks on the server side in an ajax request, just as in a 'standard' form submission. No need to change the implementation of the form!

Next, you will want to suppress the default browser behaviour of pressing a submit button, which is submitting the form in a POST request and causing the browser to make a full-page request which will cause Seaside to (re-)render the page. There are several ways to do this but simply changing the button's type is an easy way:

(html button)
   bePush;
  ...

Finally, you need to update the contents of the page. Your use of #onComplete: is correct except that this javascript code is generated when you first render the page. Hence it is rendering the value of self getResult at the moment the page was rendered. What you want is the value after you executed the form submission. This requires another callback:

(html button)
   bePush;
   onClick: ((html jQuery ajax)
       serializeForm;
       callback: [ self convert ];
       onComplete: ((html jQuery: '#txtResult') load html: [:r | self renderTextResultContentsOn: r]));
   id: 'calclink';
   with: 'Convert'.

UPDATE The above code performs two calls to the server, which you can optimize by combining callbacks into a single ajax request:

(html button)
   bePush;
   onClick: ((html jQuery ajax)
       serializeForm;
       script: [:s | self convert. s << ((s jQuery: '#txtResult') html: [:r | self renderTextResultContentsOn: r])]);
   id: 'calclink';
   with: 'Convert'.

Or, more elegantly:

    (html button)
       bePush;
       onClick: ((html jQuery id: #count) load
                    serializeForm;
                    html: [:r | self convert. self renderTextResultContentsOn: r]);
       with: 'Convert'.

The code above generates an ajax request that performs the form serialization (executing its server-side callbacks) and generates the script to modify the result on the page. The reason I put the self convert inside the script callback is a Seaside-gotcha: you can only specify a single 'response generating' callback on each ajax request (e.g. only a single script, html, json callback per request). This is a logical limitation since a single request can only generate a single response. You can, however, add multiple 'secondary' callbacks (e.g. the serialize form callbacks, a callback:json:, etc...) but a callback specified using #callback: is also a primary callback in the Seaside code. Hence, I needed to put the self convert inside the script callback, rather than in its own callback block.

Pena answered 21/4, 2015 at 19:25 Comment(2)
So why are two round trips to the server necessary, when only one trip is needed when using straight Javascript?Indiscipline
In your code, the value that gets rendered is not the value you just entered, but the value that was present when the script was generated. You can avoid the two round trips by combining callbacks on the ajax request though, I will update my answer to show that.Pena
R
2

"Nothing is happening" because all you do is send off an AJAX request to the image. What you seem to want however, is to trigger an update of a DOM node. This requires an AJAX request with an extra handler that updates a DOM element with the result of some rendering.

What you're missing on the image side is basically:

  • what is the id of the element you want to update?
  • what render code should be executed to generate the HTML for the replacement?

I don't use jQuery but from looking at the Seaside jQuery code I think your snippet should look something like this:

(html button)
  onClick: (html jQuery ajax
    "pass the id of the element to update"
    id: 'calclink';
    callback: [ self convert];
    "specify the method to call for rendering"
    html: [ :canvas | self basicRenderMyDivOn: canvas ];
    yourself);
  id: 'calclink';
  with: 'Convert'.
Rescissory answered 21/4, 2015 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.