Search code examples
jqueryajaxsmalltalkpharoseaside

Smalltalk Seaside - jQuery Ajax Callback


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'.
 ]

Solution

  • 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.