14 Replies Latest reply on May 13, 2006 2:06 PM by shane.bryzak

    Script.aculo.us with Seam Remoting


      Hi. I've managed to get the script.aculo.us InPlaceEditor control working with Seam Remoting and I thought I'd share this.

      I'm aware that I could have used MyFaces or other tags to achieve a similar AJAX functionality but I was keen to use script.aculo.us directly with Seam Remoting. I know that script.aculo.us is used by some JSF tags - but these are not geared towards Seam Remoting.

      I'm using the most recent versions of script.aculo.us and prototype. I'm also using a recent version of Seam from CVS. You'll need to read up on Seam Remoting and script.aculo.us to understand what's below.

      My solution involves extending Ajax.InPlaceEditor using the extension technique of script.aculo.us. Here is my extension:

      /*
      * InPlaceEditor extension for Seam Remoting.
      */
      Ajax.InPlaceEditor.prototype.__initialize = Ajax.InPlaceEditor.prototype.initialize;
      Ajax.InPlaceEditor.prototype.__onSubmit = Ajax.InPlaceEditor.prototype.onSubmit;
      Ajax.InPlaceEditor.prototype.__onComplete = Ajax.InPlaceEditor.prototype.onComplete;
      Ajax.InPlaceEditor.prototype = Object.extend(Ajax.InPlaceEditor.prototype, {
      
       initialize: function(element, componentName, componentMethodName, componentParams, componentCallback, options) {
       SeamRemote.log("initialize()\n");
       this.__initialize(element, '/seam/remoting' + SeamRemote.PATH_EXECUTE, options);
       this.setOptions(options);
       this.componentName = componentName;
       this.componentMethodName = componentMethodName;
       this.componentParams = componentParams;
       this.componentCallback = componentCallback;
       this.component = SeamRemote.create(this.componentName);
       },
      
       onSubmit: function() {
       SeamRemote.log("onSubmit()\n");
       SeamRemote.displayLoadingMessage();
       this.componentParams.push(this.editField.value);
       var call = SeamRemote.createCall(this.component, this.componentMethodName, this.componentParams, this.componentCallback);
       SeamRemote.pendingCalls.put(call.id, call);
       var envelope = SeamRemote.createEnvelope(SeamRemote.createHeader(), call.data);
       var ajaxOptions = {
       method: 'post',
       postBody: envelope
       }
       this.options.ajaxOptions = ajaxOptions;
       // this.options.ajaxOptions = Object.extend(this.options.ajaxOptions, ajaxOptions);
       return this.__onSubmit();
       },
      
       onComplete: function(transport, element) {
       SeamRemote.log("onComplete() " + this.element + "\n");
       Element.removeClassName(this.form, this.options.loadingClassName);
       SeamRemote.requestCallback(transport, SeamRemote.processResponse);
       },
      
       setOptions: function(options) {
       this.options = Object.extend(Object.extend(this.options, {
       evalScripts: true
       }), options || {});
       }
      });
      


      I'm sure there is a better way of coding much of this and I'm not sure I've done this 100% correctly. You can see that I've borrowed code from both Seam Remoting and script.aculo.us to get this working.

      In the header of my template.xhtml I have:

      <script src="/js/prototype.js" type="text/javascript"></script>
       <script src="/js/scriptaculous.js" type="text/javascript"></script>
       <script src="/seam/remoting/resource/remote.js" type="text/javascript"></script>
       <script src="/seam/remoting/interface.js?optionsBean&amp;updaterBean" type="text/javascript"></script>
       <script type="text/javascript">
       //<![CDATA[
       SeamRemote.getContext().setConversationId('${conversation.id}');
       // ]]>
       </script>
      


      I have two Seam components registered here and I am setting the conversation id. Here is how I'm using the control:

       <t:dataList id="aList" value="#{myCollection}" var="item" rowIndexVar="rowIndex"
       layout="unorderedList">
       <span id="item${rowIndex}">${item.name}</span>
       <script type="text/javascript">
       //<![CDATA[
      
       new Ajax.InPlaceEditor(
       'item${rowIndex}',
       'updaterBean',
       'updateName',
       new Array('${item.id}'),
       function(result) {
       element = document.getElementById("item${rowIndex}");
       element.innerHTML = result;
       new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
       });
       // ]]>
       </script>
       </t:dataList>
      


      This iterates over a collection of objects that have a 'name' property. The user can edit the name and the value is updated on the server via Seam Remoting.

      That's it! My application has Web 2.0 looking field editing backed up by EJB3 Seam components.

      I'll document this on the Wiki if there is interest. I also intend to have a go at making some of the other script.aculo.us AJAX controls work with Seam too.

      I'd be interested to hear what people think of this solution - in particular how it relates to Seam Remoting.

        • 1. Re: Script.aculo.us with Seam Remoting
          gavin.king

          Haha, what a coincedence, I was actually working on this on the w/e.

          • 2. Re: Script.aculo.us with Seam Remoting
            mirko27

            So share your work too Gavin.

            • 3. Re: Script.aculo.us with Seam Remoting
              gavin.king

              Sorry, not quite ready yet.

              • 4. Re: Script.aculo.us with Seam Remoting


                Ah, neither is mine. It would at least be good to have an idea of where you're going with your related work. Not least because it may save me time!

                • 5. Re: Script.aculo.us with Seam Remoting
                  shane.bryzak

                  Nicely done, I hadn't even considered integrating Seam Remoting in this manner and it is nice to see that it is actually possible. One little thing I noticed, is that where you're calling the initialize method you're not including the context path of your application in the URL:

                  this.__initialize(element, '/seam/remoting' + SeamRemote.PATH_EXECUTE, options);


                  I'm not sure how your application is being served but you may need to prefix this with SeamRemote.contextPath to make it more portable.

                  Is there anything you think that could be changed or improved to make it easier to integrate remoting with other JS frameworks?

                  • 6. Re: Script.aculo.us with Seam Remoting

                    Hi sbryzak2,

                    My application is mapped to the root context as I want to have complete control of URLs for the entire site. You're right, it would be more portable if I'd used your SeamRemote.contextPath value and I'll add this in to my code. Thanks!

                    Well done with Seam Remoting, it's pretty cool. The deep integration with Seam is vital as the other remoting libraries won't offer this. I do suggest that you add to the existing documentation (seen in CVS) as I think you could explain the capabilities further. A full example application would be good too.

                    You mention integration with other libraries. My biggest area of difficulty was merging/integrating the transport of Seam Remoting with prototype and script.aculo.us. I could probably have used the Seam Remoting transport but I ended up using prototype, as it was more closely integrated with script.aculo.us.

                    Perhaps you could consider integrating Seam Remoting with prototype somehow. As an example, I wasn't able to use a member function of a script.aculo.us class as a remoting callback. The function executed but it lost it's 'instance' context. This might not be possible to solve...

                    I'm looking forward to seeing how you take this forward. Keep up the good work!

                    • 7. Re: Script.aculo.us with Seam Remoting
                      gavin.king

                      I'm currently working on integrating SeamRemote and scriptaculous.

                      • 8. Re: Script.aculo.us with Seam Remoting
                        shane.bryzak

                         

                        "d1g" wrote:

                        As an example, I wasn't able to use a member function of a script.aculo.us class as a remoting callback. The function executed but it lost it's 'instance' context. This might not be possible to solve...


                        I usually define an anonymous function to handle situations like this:

                        function doRemoteCall() {
                         var instance = new SomeInstance();
                         var callback = function(returnVal) { instance.doSomething(returnVal); };
                         SeamRemote.create("myComponent").executeSomeMethod(callback);
                        }
                        


                        Sorry about the sparse documentation, its high on my to-do list. I've also been wracking my brains trying to think of another example application that I can write that will demonstrate all the features of the remoting framework. I didn't want to write another e-commerce example... something different.

                        • 9. Re: Script.aculo.us with Seam Remoting

                          Ok, so based on everybody's examples here I managed to get the Autocompleter working with Seam Remote. What I did was the following:

                          1. Create a extensions.js in which you extend the scriptaculous Autocompleter.Base

                          SeamAutocompleter = Class.create();
                          Object.extend(Object.extend(SeamAutocompleter.prototype, Autocompleter.Base.prototype), {
                          
                           initialize: function(element, update, options) {
                           SeamRemote.log('SeamAutocompleter.initialize()\n');
                           this.baseInitialize(element, update, options);
                           },
                          
                           getUpdatedChoices: function() {
                           SeamRemote.log('SeamAutocompleter.getUpdatedChoices()\n');
                           var instance = this;
                           var callback = function(returnVal) { instance.updateChoices(returnVal); };
                           SeamRemote.create("autoComplete").getSuggestion(this.getToken(), callback);
                           }
                          
                          });
                          


                          2. Create the Seam Remote component AutoCompleteAction.java with its interface AutoComplete.java
                          @Stateless
                          @Name("autoComplete")
                          @Interceptors(SeamInterceptor.class)
                          public class AutoCompleteAction implements AutoComplete {
                          
                           public String getSuggestion(String query) {
                          
                           // You would have a database query to get the actual
                           // suggestions based upon the user's input 'query'
                           return "<ul><li>option 1</li><li>option 2</li></ul>";
                          
                           }
                          
                          }
                          

                          @Local
                          public interface AutoComplete {
                          
                           @WebRemote
                           public String getSuggestion(String query);
                          
                          }
                          


                          3. Put the scriptaculous input box in your HTML
                          <input autocomplete="off" id="contact_name" name="contact[name]" size="30" type="text" value="" />
                          <div class="auto_complete" id="contact_name_auto_complete"></div>
                          <script type="text/javascript">
                           new SeamAutocompleter('contact_name', 'contact_name_auto_complete',{});
                          </script>
                          


                          Don't forget to import the correct JavaScript files in the HEAD section of your HTML.

                          Hope this is of any use to anybody as JavaScript debugging can be quite a pain! :)

                          • 10. Re: Script.aculo.us with Seam Remoting


                            Marcio, thanks for that!

                            I've managed to get your version of Autocomplete working in my environment too. Autocomplete is the control that wanted to get working in the first place but then I got distracted with InPlaceEditor (see first post).

                            I'm hoping that somebody who really knows what they're doing will produce a full Seam and Script.aculo.us integration solution...

                            • 11. Re: Script.aculo.us with Seam Remoting

                              Gavin,

                              Any chance your prototype + scriptaculous integration work will appear in a Seam downloadable soon?

                              Will this appears, will there changes in how Seam currently supports remoting?

                              Thanks.

                              • 12. Re: Script.aculo.us with Seam Remoting

                                Looked at how Seam might play with jMaki?

                                • 13. Re: Script.aculo.us with Seam Remoting
                                  gavin.king

                                  Sorry, I have not had any time to work on this stuff, I've been superbusy lately. It won't make it into Seam 1.0.

                                  • 14. Re: Script.aculo.us with Seam Remoting
                                    shane.bryzak

                                    I haven't really looked at the sciptaculous stuff, but what kind of integration is it that's needed?