You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by mm...@apache.org on 2005/07/11 06:46:18 UTC

svn commit: r210067 [1/2] - in /myfaces: build/trunk/ examples/trunk/simple/ examples/trunk/simple/WEB-INF/ examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/ forrest/trunk/resources/ sandbox/trunk/conf/ sandbox/trunk/src/ja...

Author: mmarinschek
Date: Sun Jul 10 21:46:15 2005
New Revision: 210067

URL: http://svn.apache.org/viewcvs?rev=210067&view=rev
Log:
got the AJAXInputSuggest (partially) working. Yes!

Added:
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxComponent.java
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxPhaseListener.java
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxRenderer.java
Modified:
    myfaces/build/trunk/NOTICE.txt
    myfaces/examples/trunk/simple/WEB-INF/examples-config.xml
    myfaces/examples/trunk/simple/ajaxInputSuggest.jsp
    myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/AjaxInputSuggest.java
    myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/TestFilter.java
    myfaces/forrest/trunk/resources/   (props changed)
    myfaces/sandbox/trunk/conf/faces-config.xml
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggest.java
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestRenderer.java
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestTag.java
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/controls.js
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/dragdrop.js
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/effects.js
    myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/prototype.js
    myfaces/share/trunk/src/java/org/apache/myfaces/renderkit/html/HtmlFormRendererBase.java
    myfaces/tomahawk/trunk/src/java/org/apache/myfaces/component/html/util/AddResource.java

Modified: myfaces/build/trunk/NOTICE.txt
URL: http://svn.apache.org/viewcvs/myfaces/build/trunk/NOTICE.txt?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/build/trunk/NOTICE.txt (original)
+++ myfaces/build/trunk/NOTICE.txt Sun Jul 10 21:46:15 2005
@@ -8,3 +8,9 @@
 
 This product includes software developed by Heng Yuan
 http://www.cs.ucla.edu/~heng/JSCookMenu
+
+This product includes software developed by Sam Stephenson (the ProtoType library)
+http://conio.net/
+
+This product includes software developed by Thomas Fuchs (the Ruby on Rails Ajax javascript extensions)
+http://script.aculo.us/

Modified: myfaces/examples/trunk/simple/WEB-INF/examples-config.xml
URL: http://svn.apache.org/viewcvs/myfaces/examples/trunk/simple/WEB-INF/examples-config.xml?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/examples/trunk/simple/WEB-INF/examples-config.xml (original)
+++ myfaces/examples/trunk/simple/WEB-INF/examples-config.xml Sun Jul 10 21:46:15 2005
@@ -14,7 +14,7 @@
 
      <managed-bean>
         <managed-bean-name>ajaxInputSuggest</managed-bean-name>
-        <managed-bean-class>org.apache.myfaces.examples.AjaxInputSuggest</managed-bean-class>
+        <managed-bean-class>org.apache.myfaces.examples.ajaxInputSuggest.AjaxInputSuggest</managed-bean-class>
         <managed-bean-scope>request</managed-bean-scope>
     </managed-bean>
 

Modified: myfaces/examples/trunk/simple/ajaxInputSuggest.jsp
URL: http://svn.apache.org/viewcvs/myfaces/examples/trunk/simple/ajaxInputSuggest.jsp?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/examples/trunk/simple/ajaxInputSuggest.jsp (original)
+++ myfaces/examples/trunk/simple/ajaxInputSuggest.jsp Sun Jul 10 21:46:15 2005
@@ -31,7 +31,7 @@
 <f:view>
 
    <h:form>
-       <s:ajaxSuggest   suggestedItemsMethod="#{ajaxInputSuggest.items}" />
+       <s:ajaxSuggest   suggestedItemsMethod="#{ajaxInputSuggest.getItems}" />
 
        <x:inputCalendar renderAsPopup="true" />
 

Modified: myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/AjaxInputSuggest.java
URL: http://svn.apache.org/viewcvs/myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/AjaxInputSuggest.java?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/AjaxInputSuggest.java (original)
+++ myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/AjaxInputSuggest.java Sun Jul 10 21:46:15 2005
@@ -1,5 +1,8 @@
 package org.apache.myfaces.examples.ajaxInputSuggest;
 
+import java.util.List;
+import java.util.ArrayList;
+
 /**
  * Created by IntelliJ IDEA.
  * User: Bierbrauer
@@ -9,8 +12,15 @@
  */
 public class AjaxInputSuggest
 {
-    public String[] getItems(String prefix)
+    public List getItems(String prefix)
     {
-        return new String[]{prefix+1,prefix+2,prefix+3};
+        List li = new ArrayList();
+        li.add(prefix+1);
+        li.add(prefix+2);        
+        li.add(prefix+3);
+        li.add(prefix+4);
+        li.add(prefix+5);
+        li.add(prefix+6);
+        return li;
     }
 }

Modified: myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/TestFilter.java
URL: http://svn.apache.org/viewcvs/myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/TestFilter.java?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/TestFilter.java (original)
+++ myfaces/examples/trunk/standard/src/java/org/apache/myfaces/examples/ajaxInputSuggest/TestFilter.java Sun Jul 10 21:46:15 2005
@@ -25,12 +25,9 @@
 
         if(request.getParameter("_")!=null)
         {            
-            BufferedReader reader = request.getReader();
-
-            String s;
-
-            while((s=reader.readLine())!=null)
-                System.out.println(s);
+            //Ajax request
+            System.out.println(request.getParameter("jsf_tree_64"));
+            System.out.println(request.getParameter("jsf_state_64"));            
         }
 
         filterChain.doFilter(servletRequest, servletResponse);

Propchange: myfaces/forrest/trunk/resources/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Jul 10 21:46:15 2005
@@ -0,0 +1 @@
+temp

Modified: myfaces/sandbox/trunk/conf/faces-config.xml
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/conf/faces-config.xml?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/conf/faces-config.xml (original)
+++ myfaces/sandbox/trunk/conf/faces-config.xml Sun Jul 10 21:46:15 2005
@@ -55,4 +55,8 @@
     
 
   </render-kit>
+
+  <lifecycle>
+      <phase-listener>org.apache.myfaces.custom.ajaxsuggest.AjaxPhaseListener</phase-listener>
+  </lifecycle>
 </faces-config>

Added: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxComponent.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxComponent.java?rev=210067&view=auto
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxComponent.java (added)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxComponent.java Sun Jul 10 21:46:15 2005
@@ -0,0 +1,14 @@
+package org.apache.myfaces.custom.ajaxsuggest;
+
+import javax.faces.context.FacesContext;
+import java.io.IOException;
+
+/**
+ * @author Martin Marinschek
+ * @version $Revision: $ $Date: $
+ *          <p/>
+ */
+public interface AjaxComponent
+{
+    void encodeAjax(FacesContext context) throws IOException;
+}

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggest.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggest.java?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggest.java (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggest.java Sun Jul 10 21:46:15 2005
@@ -1,18 +1,34 @@
+/*
+ * Copyright 2004 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.apache.myfaces.custom.ajaxsuggest;
 
 import org.apache.myfaces.component.html.ext.HtmlInputText;
 
 import javax.faces.el.MethodBinding;
 import javax.faces.context.FacesContext;
+import javax.faces.render.Renderer;
+import java.io.IOException;
 
 /**
- * Created by IntelliJ IDEA.
- * User: Bierbrauer
- * Date: 07.07.2005
- * Time: 15:48:29
- * To change this template use File | Settings | File Templates.
+ * @author Gerald Muellan (latest modification by $Author: svieujot $)
+ * @author Martin Marinschek
+ *
+ * @version $Revision: 169662 $ $Date: 2005-05-11 19:57:24 +0200 (Wed, 11 May 2005) $
  */
-public class AjaxInputSuggest extends HtmlInputText
+public class AjaxInputSuggest extends HtmlInputText implements AjaxComponent
 {
     private MethodBinding _suggestedItemsMethod;
 
@@ -42,5 +58,17 @@
         super.restoreState(context, values[0]);
         _suggestedItemsMethod = (MethodBinding) restoreAttachedState(context, values[1]);
 
+    }
+
+    public void encodeAjax(FacesContext context)
+            throws IOException
+    {
+        if (context == null) throw new NullPointerException("context");
+        if (!isRendered()) return;
+        Renderer renderer = getRenderer(context);
+        if (renderer != null && renderer instanceof AjaxRenderer)
+        {
+            ((AjaxRenderer) renderer).encodeAjax(context, this);
+        }
     }
 }

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestRenderer.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestRenderer.java?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestRenderer.java (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestRenderer.java Sun Jul 10 21:46:15 2005
@@ -21,22 +21,30 @@
 import org.apache.myfaces.renderkit.JSFAttr;
 import org.apache.myfaces.renderkit.html.HTML;
 import org.apache.myfaces.renderkit.html.ext.HtmlTextRenderer;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.context.ResponseWriter;
 import javax.faces.render.Renderer;
+import javax.faces.application.ViewHandler;
+import javax.faces.el.MethodBinding;
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * @author Gerald Muellan
  * @author Martin Marinschek
  * @version $Revision: 177984 $ $Date: 2005-05-23 19:39:37 +0200 (Mon, 23 May 2005) $
  */
-public class AjaxInputSuggestRenderer extends HtmlTextRenderer
+public class AjaxInputSuggestRenderer extends HtmlTextRenderer implements AjaxRenderer
 {
+    private static Log log = LogFactory.getLog(AjaxInputSuggestRenderer.class);
 
-       private static final String JAVASCRIPT_ENCODED = "org.apache.myfaces.ajaxsuggest.JAVASCRIPT_ENCODED";
+    private static final String JAVASCRIPT_ENCODED = "org.apache.myfaces.ajaxsuggest.JAVASCRIPT_ENCODED";
       
 
    /**
@@ -106,8 +114,19 @@
             out.writeAttribute(HTML.CLASS_ATTR, "auto_complete", null);
             out.writeAttribute(HTML.ID_ATTR,
                     component.getClientId(context)+"_auto_complete", null);
+            out.writeAttribute(HTML.STYLE_ATTR,
+                    "      position:absolute;" +
+                    "      width:250px;" +
+                    "      background-color:white;" +
+                    "      border:1px solid #888;" +
+                    "      margin:0px;" +
+                    "      padding:0px;",null);
             out.endElement(HTML.DIV_ELEM);
 
+            String viewId = context.getViewRoot().getViewId();
+            ViewHandler viewHandler = context.getApplication().getViewHandler();
+            String actionURL = viewHandler.getActionURL(context, viewId);
+
             out.startElement(HTML.SCRIPT_ELEM, null);
             out.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
             out.writeText("new Ajax.Autocompleter('",null);
@@ -115,15 +134,55 @@
             out.writeText("','",null);
             out.writeText(component.getClientId(context)+"_auto_complete",null);
             out.writeText("','",null);
-            out.writeText("/demos/ajax/auto_suggest",null);
+            out.writeText(context.getExternalContext().encodeActionURL(actionURL+"?affectedAjaxComponent="+component.getClientId(context)),null);
             out.writeText("', {\n" +
                     "      method:       'post',\n" +
                     "      asynchronous: true,\n" +
-                    "      parameters:   'jsf_tree_64='+/*document.getElementById('jsf_tree_64').value+*/'&jsf_state_64='/*+document.getElementById('jsf_state_64').value*/" +
+                    "      parameters:   'jsf_tree_64='+encodeURIComponent(document.getElementById('jsf_tree_64').value)+'&jsf_state_64='+encodeURIComponent(document.getElementById('jsf_state_64').value)+'&jsf_viewid='+encodeURIComponent(document.getElementById('jsf_viewid').value)" +
                     "    })",null);
             out.endElement(HTML.SCRIPT_ELEM);
+    }
 
-
+    public void decode(FacesContext facesContext, UIComponent component)
+    {
+        super.decode(facesContext, component);
     }
 
+    public void encodeAjax(FacesContext context, UIComponent uiComponent) throws IOException
+    {
+        if(!(uiComponent instanceof AjaxInputSuggest))
+            throw new IllegalArgumentException("Component no instance of AjaxInputSuggest");
+
+
+        AjaxInputSuggest ajaxInputSuggest = (AjaxInputSuggest) uiComponent;
+
+        MethodBinding mb = ajaxInputSuggest.getSuggestedItemsMethod();
+
+        List li = (List) mb.invoke(context,new Object[]{context.getExternalContext().getRequestParameterMap().get(
+                uiComponent.getClientId(context))});
+
+        StringBuffer buf = new StringBuffer();
+        buf.append("<ul>");
+
+        for (int i = 0; i < li.size(); i++)
+        {
+            buf.append("<li>");
+            buf.append((String) li.get(i));
+            buf.append("</li>");
+        }
+        buf.append("</ul>");
+
+        buf.insert(0,Integer.toHexString(buf.length())+"\r\n");
+        buf.append("\r\n"+0+"\r\n\r\n");
+
+        if(context.getExternalContext().getResponse() instanceof HttpServletResponse)
+        {
+            ((HttpServletResponse)context.getExternalContext().getResponse()).addHeader(
+                    "Transfer-Encoding", "chunked");
+            PrintWriter writer = ((HttpServletResponse) context.getExternalContext().getResponse()).getWriter();
+            writer.print(buf.toString());
+            writer.flush();
+            writer.close();
+        }
+    }
 }

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestTag.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestTag.java?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestTag.java (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxInputSuggestTag.java Sun Jul 10 21:46:15 2005
@@ -27,8 +27,8 @@
 import javax.faces.el.MethodBinding;
 
 /**
- * @author Sean Schofield
- * @author Matt Blum
+ * @author Gerald Muellan
+ * @author Martin Marinschek
  * @version $Revision: $ $Date: $
  */
 public class AjaxInputSuggestTag extends HtmlInputTagBase

Added: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxPhaseListener.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxPhaseListener.java?rev=210067&view=auto
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxPhaseListener.java (added)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxPhaseListener.java Sun Jul 10 21:46:15 2005
@@ -0,0 +1,70 @@
+/**
+ * Copyright 2004 by Irian Marinschek & Spiegl Software OEG
+ */
+package org.apache.myfaces.custom.ajaxsuggest;
+
+import org.apache.myfaces.component.html.util.HtmlComponentUtils;
+import org.apache.myfaces.renderkit.html.HtmlRendererUtils;
+import org.apache.myfaces.renderkit.RendererUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.faces.event.PhaseListener;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseId;
+import javax.faces.context.FacesContext;
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.UIComponent;
+import java.util.Iterator;
+import java.io.IOException;
+
+/**
+ * @author Martin Marinschek
+ * @version $Revision: $ $Date: $
+ *          <p/>
+ *          $Log: $
+ */
+public class AjaxPhaseListener implements PhaseListener
+{
+    private static Log log=LogFactory.getLog(AjaxPhaseListener.class);
+
+    public void afterPhase(PhaseEvent event)
+    {
+        //too late here ;)
+    }
+
+    public void beforePhase(PhaseEvent event)
+    {
+        FacesContext context = event.getFacesContext();
+
+        if(context.getExternalContext().getRequestParameterMap().containsKey("affectedAjaxComponent"))
+        {
+            UIViewRoot root = context.getViewRoot();
+
+            UIComponent ajaxComponent = root.findComponent((String)context.getExternalContext().getRequestParameterMap().get("affectedAjaxComponent"));
+
+            if(ajaxComponent instanceof AjaxComponent)
+            {
+                try
+                {
+                    ((AjaxComponent) ajaxComponent).encodeAjax(context);
+                }
+                catch (IOException e)
+                {
+                    log.error("Exception while rendering ajax-response",e);
+                }
+            }
+            else
+            {
+                log.error("Found component is no ajaxComponent : "+RendererUtils.getPathToComponent(ajaxComponent));
+            }
+
+            context.responseComplete();
+        }
+    }
+
+    public PhaseId getPhaseId()
+    {
+        return PhaseId.RENDER_RESPONSE;
+    }
+}

Added: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxRenderer.java
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxRenderer.java?rev=210067&view=auto
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxRenderer.java (added)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/AjaxRenderer.java Sun Jul 10 21:46:15 2005
@@ -0,0 +1,16 @@
+package org.apache.myfaces.custom.ajaxsuggest;
+
+import javax.faces.context.FacesContext;
+import javax.faces.component.UIComponent;
+import java.io.IOException;
+
+/**
+ * @author Martin Marinschek
+ * @version $Revision: $ $Date: $
+ *          <p/>
+ *          $Log: $
+ */
+public interface AjaxRenderer
+{
+    void encodeAjax(FacesContext context, UIComponent component) throws IOException;
+}

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/controls.js
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/controls.js?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/controls.js (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/controls.js Sun Jul 10 21:46:15 2005
@@ -1,5 +1,6 @@
 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// 
+//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
@@ -7,10 +8,10 @@
 // distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so, subject to
 // the following conditions:
-// 
+//
 // The above copyright notice and this permission notice shall be
 // included in all copies or substantial portions of the Software.
-// 
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -19,12 +20,11 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-
 Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
   var children = $(element).childNodes;
   var text     = "";
   var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
-  
+
   for (var i = 0; i < children.length; i++) {
     if(children[i].nodeType==3) {
       text+=children[i].nodeValue;
@@ -33,62 +33,90 @@
         text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
     }
   }
-  
+
   return text;
 }
 
-Ajax.Autocompleter = Class.create();
-Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({
-  initialize: function(element, update, url, options) {
-    this.element     = $(element); 
-    this.update      = $(update);  
-    this.has_focus   = false; 
-    this.changed     = false; 
-    this.active      = false; 
-    this.index       = 0;     
-    this.entry_count = 0;    
-    this.url         = url;
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getEntry(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: new Array (',', '\n') } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
 
-    this.setOptions(options);
-    this.options.asynchronous = true;
-    this.options.onComplete   = this.onComplete.bind(this)
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+  base_initialize: function(element, update, options) {
+    this.element     = $(element);
+    this.update      = $(update);
+    this.has_focus   = false;
+    this.changed     = false;
+    this.active      = false;
+    this.index       = 0;
+    this.entry_count = 0;
+
+    if (this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = {}
+
+    this.options.tokens       = this.options.tokens || new Array();
     this.options.frequency    = this.options.frequency || 0.4;
     this.options.min_chars    = this.options.min_chars || 1;
-    this.options.method       = 'post';
-    
-    this.options.onShow = this.options.onShow || 
-      function(element, update){ 
-        if(!update.style.position || update.style.position=='absolute') {
-          update.style.position = 'absolute';
+    this.options.onShow       = this.options.onShow ||
+    function(element, update){
+      if(!update.style.position || update.style.position=='absolute') {
+        update.style.position = 'absolute';
           var offsets = Position.cumulativeOffset(element);
           update.style.left = offsets[0] + 'px';
           update.style.top  = (offsets[1] + element.offsetHeight) + 'px';
           update.style.width = element.offsetWidth + 'px';
-        }
-        new Effect.Appear(update,{duration:0.3});
-      };
-    this.options.onHide = this.options.onHide || 
-      function(element, update){ new Effect.Fade(update,{duration:0.3}) };
-    
-    
+      }
+      new Effect.Appear(update,{duration:0.15});
+    };
+    this.options.onHide = this.options.onHide ||
+    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
     if(this.options.indicator)
       this.indicator = $(this.options.indicator);
-       
+
+    if (typeof(this.options.tokens) == 'string')
+      this.options.tokens = new Array(this.options.tokens);
+
     this.observer = null;
-    
+
     Element.hide(this.update);
-    
+
     Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
     Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
   },
-  
+
   show: function() {
     if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
     if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
-      new Insertion.After(this.update, 
+      new Insertion.After(this.update,
        '<iframe id="' + this.update.id + '_iefix" '+
-       'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(apacity=0);" ' +
-       'src="javascript:;" frameborder="0" scrolling="no"></iframe>');
+       'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
       this.iefix = $(this.update.id+'_iefix');
     }
     if(this.iefix) {
@@ -98,70 +126,20 @@
       Element.show(this.iefix);
     }
   },
-  
+
   hide: function() {
     if(this.update.style.display=='') this.options.onHide(this.element, this.update);
     if(this.iefix) Element.hide(this.iefix);
   },
-  
+
   startIndicator: function() {
     if(this.indicator) Element.show(this.indicator);
   },
-  
+
   stopIndicator: function() {
     if(this.indicator) Element.hide(this.indicator);
   },
-  
-  onObserverEvent: function() {
-    this.changed = false;   
-    if(this.element.value.length>=this.options.min_chars) {
-      this.startIndicator();
-
-      if(this.options.parameters && this.options.parameters.length>0){
-        this.options.parameters += '&';
-      } else {
-        this.options.parameters = '';
-      }
-      this.options.parameters += this.options.callback ?
-        this.options.callback(this.element, Form.Element.getValue(this.element)) :
-          Form.Element.serialize(this.element);
-      new Ajax.Request(this.url, this.options);
-    } else {
-      this.active = false;
-      this.hide();
-    }
-  },
-  
-  addObservers: function(element) {
-    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
-    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
-  },
-  
-  onComplete: function(request) {
-    if(!this.changed && this.has_focus) {
-      this.update.innerHTML = request.responseText;
-      Element.cleanWhitespace(this.update);
-      Element.cleanWhitespace(this.update.firstChild);
 
-      if(this.update.firstChild && this.update.firstChild.childNodes) {
-        this.entry_count = 
-          this.update.firstChild.childNodes.length;
-        for (var i = 0; i < this.entry_count; i++) {
-          entry = this.get_entry(i);
-          entry.autocompleteIndex = i;
-          this.addObservers(entry);
-        }
-      } else { 
-        this.entry_count = 0;
-      }
-      
-      this.stopIndicator();
-      
-      this.index = 0;
-      this.render();
-    }
-  },
-  
   onKeyPress: function(event) {
     if(this.active)
       switch(event.keyCode) {
@@ -187,81 +165,276 @@
          if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
          return;
       }
-     else 
-      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
+     else
+      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
         return;
-    
+
     this.changed = true;
     this.has_focus = true;
-    
+
     if(this.observer) clearTimeout(this.observer);
-      this.observer = 
+      this.observer =
         setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
   },
-  
+
   onHover: function(event) {
     var element = Event.findElement(event, 'LI');
-    if(this.index != element.autocompleteIndex) 
+    if(this.index != element.autocompleteIndex)
     {
         this.index = element.autocompleteIndex;
         this.render();
     }
     Event.stop(event);
   },
-  
+
   onClick: function(event) {
     var element = Event.findElement(event, 'LI');
     this.index = element.autocompleteIndex;
     this.select_entry();
     Event.stop(event);
   },
-  
+
   onBlur: function(event) {
     // needed to make click events working
     setTimeout(this.hide.bind(this), 250);
     this.has_focus = false;
-    this.active = false;     
-  }, 
-  
+    this.active = false;
+  },
+
   render: function() {
     if(this.entry_count > 0) {
       for (var i = 0; i < this.entry_count; i++)
-        this.index==i ? 
-          Element.addClassName(this.get_entry(i),"selected") : 
+        this.index==i ?
+          Element.addClassName(this.get_entry(i),"selected") :
           Element.removeClassName(this.get_entry(i),"selected");
-        
-      if(this.has_focus) { 
-        if(this.get_current_entry().scrollIntoView) 
+
+      if(this.has_focus) {
+        if(this.get_current_entry().scrollIntoView)
           this.get_current_entry().scrollIntoView(false);
-        
+
         this.show();
         this.active = true;
       }
     } else this.hide();
   },
-  
+
   mark_previous: function() {
     if(this.index > 0) this.index--
       else this.index = this.entry_count-1;
   },
-  
+
   mark_next: function() {
     if(this.index < this.entry_count-1) this.index++
       else this.index = 0;
   },
-  
+
   get_entry: function(index) {
     return this.update.firstChild.childNodes[index];
   },
-  
+
   get_current_entry: function() {
     return this.get_entry(this.index);
   },
-  
+
   select_entry: function() {
     this.active = false;
     value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
-    this.element.value = value;
+    this.updateElement(value);
     this.element.focus();
+  },
+
+  updateElement: function(value) {
+    var last_token_pos = this.findLastToken();
+    if (last_token_pos != -1) {
+      var new_value = this.element.value.substr(0, last_token_pos + 1);
+      var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
+      if (whitespace)
+        new_value += whitespace[0];
+      this.element.value = new_value + value;
+    } else {
+      this.element.value = value;
+    }
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.has_focus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.firstChild);
+
+      if(this.update.firstChild && this.update.firstChild.childNodes) {
+        this.entry_count =
+          this.update.firstChild.childNodes.length;
+        for (var i = 0; i < this.entry_count; i++) {
+          entry = this.get_entry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else {
+        this.entry_count = 0;
+      }
+
+      this.stopIndicator();
+
+      this.index = 0;
+      this.render();
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;
+    if(this.getEntry().length>=this.options.min_chars) {
+      this.startIndicator();
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+
+  getEntry: function() {
+    var token_pos = this.findLastToken();
+    if (token_pos != -1)
+      var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+    else
+      var ret = this.element.value;
+
+    return /\n/.test(ret) ? '' : ret;
+  },
+
+  findLastToken: function() {
+    var last_token_pos = -1;
+
+    for (var i=0; i<this.options.tokens.length; i++) {
+      var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
+      if (this_token_pos > last_token_pos)
+        last_token_pos = this_token_pos;
+    }
+    return last_token_pos;
+  }
+}
+
+Ajax.Autocompleter = Class.create();
+Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
+Object.extend(new Ajax.Base(), {
+  initialize: function(element, update, url, options) {
+	  this.base_initialize(element, update, options);
+    this.options.asynchronous = true;
+    this.options.onComplete   = this.onComplete.bind(this)
+    this.options.method       = 'post';
+    this.url                  = url;
+  },
+
+  getUpdatedChoices: function() {
+    entry = encodeURIComponent(this.element.name) + '=' +
+      encodeURIComponent(this.getEntry());
+
+      if(this.options.parameters && this.options.parameters.length>0){
+        this.options.parameters += '&';
+      } else {
+        this.options.parameters = '';
+      }
+      this.options.parameters += this.options.callback ?
+        this.options.callback(this.element, entry) : entry;
+
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+
+}));
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partial_search - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option full_search to true (default: off).
+//
+// - full_search - Search anywhere in autocomplete array strings.
+//
+// - partial_chars - How many characters to enter before triggering
+//                   a partial match (unlike min_chars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignore_case - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+  initialize: function(element, update, array, options) {
+    this.base_initialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partial_search: true,
+      partial_chars: 2,
+      ignore_case: true,
+      full_search: false,
+      selector: function(instance) {
+        var ret       = new Array(); // Beginning matches
+        var partial   = new Array(); // Inside matches
+        var entry     = instance.getEntry();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&
+            ret.length < instance.options.choices ; i++) {
+          var elem = instance.options.array[i];
+          var found_pos = instance.options.ignore_case ?
+            elem.toLowerCase().indexOf(entry.toLowerCase()) :
+            elem.indexOf(entry);
+
+          if (found_pos == 0 && elem.length == entry.length)
+            continue;
+          else if (found_pos == 0)
+            ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+              elem.substr(entry.length) + "</li>");
+          else if (entry.length >= instance.options.partial_chars &&
+            instance.options.partial_search && found_pos != -1) {
+            if (!instance.options.full_search && !/\s/.test(elem.substr(found_pos-1,1)))
+              continue;
+            partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
+              elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
+              found_pos + entry.length) + "</li>")
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || {});
   }
-});
\ No newline at end of file
+});

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/dragdrop.js
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/dragdrop.js?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/dragdrop.js (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/dragdrop.js Sun Jul 10 21:46:15 2005
@@ -1,7 +1,7 @@
 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-// 
+//
 // Element.Class part Copyright (c) 2005 by Rick Olson
-// 
+//
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
@@ -9,10 +9,10 @@
 // distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so, subject to
 // the following conditions:
-// 
+//
 // The above copyright notice and this permission notice shall be
 // included in all copies or substantial portions of the Software.
-// 
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -70,7 +70,7 @@
       }
       return true;
     },
-    
+
     // expects arrays of strings and/or strings as optional paramters
     // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
     has_any: function(element) {
@@ -78,7 +78,7 @@
       if(!element || !element.className) return false;
       var regEx;
       for(var i = 1; i < arguments.length; i++) {
-        if((typeof arguments[i] == 'object') && 
+        if((typeof arguments[i] == 'object') &&
           (arguments[i].constructor == Array)) {
           for(var j = 0; j < arguments[i].length; j++) {
             regEx = new RegExp("\\b" + arguments[i][j] + "\\b");
@@ -91,18 +91,18 @@
       }
       return false;
     },
-    
+
     childrenWith: function(element, className) {
       var children = $(element).getElementsByTagName('*');
       var elements = new Array();
-      
+
       for (var i = 0; i < children.length; i++) {
         if (Element.Class.has(children[i], className)) {
           elements.push(children[i]);
           break;
         }
       }
-      
+
       return elements;
     }
 }
@@ -111,104 +111,104 @@
 
 var Droppables = {
   drops: false,
-  
+
+  remove: function(element) {
+    for(var i = 0; i < this.drops.length; i++)
+      if(this.drops[i].element == element)
+        this.drops.splice(i,1);
+  },
+
   add: function(element) {
     var element = $(element);
-    var options = {
+    var options = Object.extend({
       greedy:     true,
-      hoverclass: null  
-    }.extend(arguments[1] || {});
-    
+      hoverclass: null
+    }, arguments[1] || {});
+
     // cache containers
     if(options.containment) {
       options._containers = new Array();
       var containment = options.containment;
-      if((typeof containment == 'object') && 
+      if((typeof containment == 'object') &&
         (containment.constructor == Array)) {
         for(var i=0; i<containment.length; i++)
           options._containers.push($(containment[i]));
       } else {
         options._containers.push($(containment));
       }
-      options._containers_length = 
+      options._containers_length =
         options._containers.length-1;
     }
-    
-    if(element.style.position=='') //fix IE
-      element.style.position = 'relative'; 
-    
+
+    Element.makePositioned(element); // fix IE
+
+    options.element = element;
+
     // activate the droppable
-    element.droppable = options;
-    
     if(!this.drops) this.drops = [];
-    this.drops.push(element);
+    this.drops.push(options);
   },
-  
+
   is_contained: function(element, drop) {
-    var containers = drop.droppable._containers;
+    var containers = drop._containers;
     var parentNode = element.parentNode;
-    var i = drop.droppable._containers_length;
+    var i = drop._containers_length;
     do { if(parentNode==containers[i]) return true; } while (i--);
     return false;
   },
-  
+
   is_affected: function(pX, pY, element, drop) {
     return (
-      (drop!=element) &&
-      ((!drop.droppable._containers) ||
+      (drop.element!=element) &&
+      ((!drop._containers) ||
         this.is_contained(element, drop)) &&
-      ((!drop.droppable.accept) ||
-        (Element.Class.has_any(element, drop.droppable.accept))) &&
-      Position.within(drop, pX, pY) );
+      ((!drop.accept) ||
+        (Element.Class.has_any(element, drop.accept))) &&
+      Position.within(drop.element, pX, pY) );
   },
-  
+
   deactivate: function(drop) {
-    Element.Class.remove(drop, drop.droppable.hoverclass);
+    Element.Class.remove(drop.element, drop.hoverclass);
     this.last_active = null;
   },
-  
+
   activate: function(drop) {
     if(this.last_active) this.deactivate(this.last_active);
-    if(drop.droppable.hoverclass) {
-      Element.Class.add(drop, drop.droppable.hoverclass);
+    if(drop.hoverclass) {
+      Element.Class.add(drop.element, drop.hoverclass);
       this.last_active = drop;
     }
   },
-  
+
   show: function(event, element) {
     if(!this.drops) return;
     var pX = Event.pointerX(event);
     var pY = Event.pointerY(event);
     Position.prepare();
-    
+
     var i = this.drops.length-1; do {
       var drop = this.drops[i];
       if(this.is_affected(pX, pY, element, drop)) {
-        if(drop.droppable.onHover)
-           drop.droppable.onHover(
-            element, drop, Position.overlap(drop.droppable.overlap, drop));
-        if(drop.droppable.greedy) { 
+        if(drop.onHover)
+           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+        if(drop.greedy) {
           this.activate(drop);
           return;
         }
       }
     } while (i--);
   },
-  
+
   fire: function(event, element) {
-    if(!this.drops) return;
-    var pX = Event.pointerX(event);
-    var pY = Event.pointerY(event);
+    if(!this.last_active) return;
     Position.prepare();
-    
-    var i = this.drops.length-1; do {
-      var drop = this.drops[i];
-      if(this.is_affected(pX, pY, element, drop))
-        if(drop.droppable.onDrop)
-           drop.droppable.onDrop(element);
-    } while (i--);
+
+    if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
+      if (this.last_active.onDrop)
+        this.last_active.onDrop(element, this.last_active);
+
   },
-  
+
   reset: function() {
     if(this.last_active)
       this.deactivate(this.last_active);
@@ -218,7 +218,12 @@
 Draggables = {
   observers: new Array(),
   addObserver: function(observer) {
-    this.observers.push(observer);    
+    this.observers.push(observer);
+  },
+  removeObserver: function(element) {  // element instead of obsever fixes mem leaks
+    for(var i = 0; i < this.observers.length; i++)
+      if(this.observers[i].element && (this.observers[i].element == element))
+        this.observers.splice(i,1);
   },
   notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
     for(var i = 0; i < this.observers.length; i++)
@@ -231,29 +236,26 @@
 Draggable = Class.create();
 Draggable.prototype = {
   initialize: function(element) {
-    var options = {
+    var options = Object.extend({
       handle: false,
-      starteffect: function(element) { 
-        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
+      starteffect: function(element) {
+        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
       },
       reverteffect: function(element, top_offset, left_offset) {
         new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4});
       },
-      endeffect: function(element) { 
-         new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+      endeffect: function(element) {
+         new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
       },
       zindex: 1000,
       revert: false
-    }.extend(arguments[1] || {});
-    
+    }, arguments[1] || {});
+
     this.element      = $(element);
-    this.element.drag = this;
     this.handle       = options.handle ? $(options.handle) : this.element;
-    
-    // fix IE
-    if(!this.element.style.position)
-      this.element.style.position = 'relative';
-    
+
+    Element.makePositioned(this.element); // fix IE
+
     this.offsetX      = 0;
     this.offsetY      = 0;
     this.originalLeft = this.currentLeft();
@@ -261,15 +263,27 @@
     this.originalX    = this.element.offsetLeft;
     this.originalY    = this.element.offsetTop;
     this.originalZ    = parseInt(this.element.style.zIndex || "0");
-    
+
     this.options      = options;
-    
+
     this.active       = false;
-    this.dragging     = false;   
-    
-    Event.observe(this.handle, "mousedown", this.startDrag.bindAsEventListener(this));
-    Event.observe(document, "mouseup", this.endDrag.bindAsEventListener(this));
-    Event.observe(document, "mousemove", this.update.bindAsEventListener(this));
+    this.dragging     = false;
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+    this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    Event.observe(document, "mouseup", this.eventMouseUp);
+    Event.observe(document, "mousemove", this.eventMouseMove);
+    Event.observe(document, "keypress", this.eventKeypress);
+  },
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    Event.stopObserving(document, "keypress", this.eventKeypress);
   },
   currentLeft: function() {
     return parseInt(this.element.style.left || '0');
@@ -280,41 +294,53 @@
   startDrag: function(event) {
     if(Event.isLeftClick(event)) {
       this.active = true;
-      
+
       var style = this.element.style;
       this.originalY = this.element.offsetTop  - this.currentTop()  - this.originalTop;
       this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;
       this.offsetY =  event.clientY - this.originalY - this.originalTop;
       this.offsetX =  event.clientX - this.originalX - this.originalLeft;
-      
+
       Event.stop(event);
     }
   },
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+
+    if(success) Droppables.fire(event, this.element);
+    Draggables.notify('onEnd', this);
+
+    var revert = this.options.revert;
+    if(revert && typeof revert == 'function') revert = revert(this.element);
+
+    if(revert && this.options.reverteffect) {
+      this.options.reverteffect(this.element,
+      this.currentTop()-this.originalTop,
+      this.currentLeft()-this.originalLeft);
+    } else {
+      this.originalLeft = this.currentLeft();
+      this.originalTop  = this.currentTop();
+    }
+
+    this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect)
+      this.options.endeffect(this.element);
+
+    Droppables.reset();
+  },
+  keyPress: function(event) {
+    if(this.active) {
+      if(event.keyCode==Event.KEY_ESC) {
+        this.finishDrag(event, false);
+        Event.stop(event);
+      }
+    }
+  },
   endDrag: function(event) {
     if(this.active && this.dragging) {
-      this.active = false;
-      this.dragging = false;
-      
-      Droppables.fire(event, this.element);
-      Draggables.notify('onEnd', this);
-      
-      var revert = this.options.revert;
-      if(revert && typeof revert == 'function') revert = revert(this.element);
-      
-      if(revert && this.options.reverteffect) {
-        this.options.reverteffect(this.element, 
-          this.currentTop()-this.originalTop,
-          this.currentLeft()-this.originalLeft);
-      } else {
-        this.originalLeft = this.currentLeft();
-        this.originalTop  = this.currentTop();
-      }
-      this.element.style.zIndex = this.originalZ;
-     
-      if(this.options.endeffect) 
-        this.options.endeffect(this.element);
-      
-      Droppables.reset();
+      this.finishDrag(event, true);
       Event.stop(event);
     }
     this.active = false;
@@ -340,14 +366,14 @@
         Draggables.notify('onStart', this);
         if(this.options.starteffect) this.options.starteffect(this.element);
       }
-      
+
       Droppables.show(event, this.element);
       this.draw(event);
       if(this.options.change) this.options.change(this);
-      
+
       // fix AppleWebKit rendering
-      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); 
-      
+      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+
       Event.stop(event);
    }
   }
@@ -365,16 +391,39 @@
   onStart: function() {
     this.lastValue = Sortable.serialize(this.element);
   },
-  onEnd: function() {    
+  onEnd: function() {
     if(this.lastValue != Sortable.serialize(this.element))
       this.observer(this.element)
   }
 }
 
 Sortable = {
+  sortables: new Array(),
+  options: function(element){
+    var element = $(element);
+    for(var i=0;i<this.sortables.length;i++)
+      if(this.sortables[i].element == element)
+        return this.sortables[i];
+    return null;
+  },
+  destroy: function(element){
+    var element = $(element);
+    for(var i=0;i<this.sortables.length;i++) {
+      if(this.sortables[i].element == element) {
+        var s = this.sortables[i];
+        Draggables.removeObserver(s.element);
+        for(var j=0;j<s.droppables.length;j++)
+          Droppables.remove(s.droppables[j]);
+        for(var j=0;j<s.draggables.length;j++)
+          s.draggables[j].destroy();
+        this.sortables.splice(i,1);
+      }
+    }
+  },
   create: function(element) {
     var element = $(element);
-    var options = { 
+    var options = Object.extend({
+      element:     element,
       tag:         'li',       // assumes li children, override with tag: 'tagname'
       overlap:     'vertical', // one of 'vertical', 'horizontal'
       constraint:  'vertical', // one of 'vertical', 'horizontal', false
@@ -384,9 +433,11 @@
       hoverclass:  null,
       onChange:    function() {},
       onUpdate:    function() {}
-    }.extend(arguments[1] || {});
-    element.sortable = options;
-    
+    }, arguments[1] || {});
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
     // build options for the draggables
     var options_for_draggable = {
       revert:      true,
@@ -400,30 +451,30 @@
       options_for_draggable.endeffect = options.endeffect;
     if(options.zindex)
       options_for_draggable.zindex = options.zindex;
-    
-    // build options for the droppables  
+
+    // build options for the droppables
     var options_for_droppable = {
       overlap:     options.overlap,
       containment: options.containment,
       hoverclass:  options.hoverclass,
-      onHover: function(element, dropon, overlap) { 
+      onHover: function(element, dropon, overlap) {
         if(overlap>0.5) {
           if(dropon.previousSibling != element) {
             var oldParentNode = element.parentNode;
             element.style.visibility = "hidden"; // fix gecko rendering
             dropon.parentNode.insertBefore(element, dropon);
-            if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 
+            if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)
               oldParentNode.sortable.onChange(element);
             if(dropon.parentNode.sortable)
               dropon.parentNode.sortable.onChange(element);
           }
-        } else {                
+        } else {
           var nextElement = dropon.nextSibling || null;
           if(nextElement != element) {
             var oldParentNode = element.parentNode;
             element.style.visibility = "hidden"; // fix gecko rendering
             dropon.parentNode.insertBefore(element, nextElement);
-            if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) 
+            if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)
               oldParentNode.sortable.onChange(element);
             if(dropon.parentNode.sortable)
               dropon.parentNode.sortable.onChange(element);
@@ -433,42 +484,52 @@
     }
 
     // fix for gecko engine
-    Element.cleanWhitespace(element); 
-    
-    // for onupdate
-    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
-    
-    // make it so 
+    Element.cleanWhitespace(element);
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // make it so
     var elements = element.childNodes;
-    for (var i = 0; i < elements.length; i++) 
+    for (var i = 0; i < elements.length; i++)
       if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() &&
         (!options.only || (Element.Class.has(elements[i], options.only)))) {
-        
+
         // handles are per-draggable
-        var handle = options.handle ? 
+        var handle = options.handle ?
           Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
-        
-        new Draggable(elements[i], options_for_draggable.extend({ handle: handle }));
+
+        options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
+
         Droppables.add(elements[i], options_for_droppable);
+        options.droppables.push(elements[i]);
+
       }
-      
+
+    // keep reference
+    this.sortables.push(options);
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
   },
   serialize: function(element) {
     var element = $(element);
-    var options = {
-      tag:  element.sortable.tag,
-      only: element.sortable.only,
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag:  sortableOptions.tag,
+      only: sortableOptions.only,
       name: element.id
-    }.extend(arguments[1] || {});
-    
+    }, arguments[1] || {});
+
     var items = $(element).childNodes;
     var queryComponents = new Array();
- 
+
     for(var i=0; i<items.length; i++)
       if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
         (!options.only || (Element.Class.has(items[i], options.only))))
         queryComponents.push(
-          encodeURIComponent(options.name) + "[]=" + 
+          encodeURIComponent(options.name) + "[]=" +
           encodeURIComponent(items[i].id.split("_")[1]));
 
     return queryComponents.join("&");

Modified: myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/effects.js
URL: http://svn.apache.org/viewcvs/myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/effects.js?rev=210067&r1=210066&r2=210067&view=diff
==============================================================================
--- myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/effects.js (original)
+++ myfaces/sandbox/trunk/src/java/org/apache/myfaces/custom/ajaxsuggest/resource/effects.js Sun Jul 10 21:46:15 2005
@@ -2,7 +2,7 @@
 //
 // Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
 // Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
-// 
+//
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
@@ -10,10 +10,10 @@
 // distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so, subject to
 // the following conditions:
-// 
+//
 // The above copyright notice and this permission notice shall be
 // included in all copies or substantial portions of the Software.
-// 
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -46,15 +46,33 @@
   return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
 }
 Effect.Transitions.pulse = function(pos) {
-   return (Math.floor(pos*10) % 2 == 0 ? 
+  return (Math.floor(pos*10) % 2 == 0 ?
     (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
 }
-
 Effect.Transitions.none = function(pos) {
-    return 0;
+  return 0;
 }
 Effect.Transitions.full = function(pos) {
-    return 1;
+  return 1;
+}
+
+/* ------------- element ext -------------- */
+
+Element.makePositioned = function(element) {
+  element = $(element);
+  if(element.style.position == "")
+    element.style.position = "relative";
+}
+
+Element.makeClipping = function(element) {
+  element = $(element);
+  element._overflow = element.style.overflow || 'visible';
+  if(element._overflow!='hidden') element.style.overflow = 'hidden';
+}
+
+Element.undoClipping = function(element) {
+  element = $(element);
+  if(element._overflow!='hidden') element.style.overflow = element._overflow;
 }
 
 /* ------------- core effects ------------- */
@@ -62,33 +80,33 @@
 Effect.Base = function() {};
 Effect.Base.prototype = {
   setOptions: function(options) {
-    this.options = {
+    this.options = Object.extend({
       transition: Effect.Transitions.sinoidal,
       duration:   1.0,   // seconds
       fps:        25.0,  // max. 100fps
       sync:       false, // true for combining
       from:       0.0,
       to:         1.0
-    }.extend(options || {});
+    }, options || {});
   },
   start: function(options) {
     this.setOptions(options || {});
-    this.currentFrame    = 0;
-    this.startOn  = new Date().getTime();
-    this.finishOn = this.startOn + (this.options.duration*1000);
+    this.currentFrame = 0;
+    this.startOn      = new Date().getTime();
+    this.finishOn     = this.startOn + (this.options.duration*1000);
     if(this.options.beforeStart) this.options.beforeStart(this);
-    if(!this.options.sync) this.loop();  
+    if(!this.options.sync) this.loop();
   },
   loop: function() {
-    timePos = new Date().getTime();
+    var timePos = new Date().getTime();
     if(timePos >= this.finishOn) {
       this.render(this.options.to);
-      if(this.finish) this.finish(); 
+      if(this.finish) this.finish();
       if(this.options.afterFinish) this.options.afterFinish(this);
-      return;  
+      return;
     }
-    pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
-    frame = Math.round(pos * this.options.fps * this.options.duration);
+    var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
+    var frame = Math.round(pos * this.options.fps * this.options.duration);
     if(frame > this.currentFrame) {
       this.render(pos);
       this.currentFrame = frame;
@@ -97,11 +115,11 @@
   },
   render: function(pos) {
     if(this.options.transition) pos = this.options.transition(pos);
-    pos  = pos * (this.options.to-this.options.from);
-    pos += this.options.from; 
+    pos *= (this.options.to-this.options.from);
+    pos += this.options.from;
     if(this.options.beforeUpdate) this.options.beforeUpdate(this);
     if(this.update) this.update(pos);
-    if(this.options.afterUpdate) this.options.afterUpdate(this);  
+    if(this.options.afterUpdate) this.options.afterUpdate(this);
   },
   cancel: function() {
     if(this.timeout) clearTimeout(this.timeout);
@@ -109,37 +127,37 @@
 }
 
 Effect.Parallel = Class.create();
-  Effect.Parallel.prototype = (new Effect.Base()).extend({
-    initialize: function(effects) {
-      this.effects = effects || [];
-       this.start(arguments[1]);
-    },
-    update: function(position) {
-       for (var i = 0; i < this.effects.length; i++)
-        this.effects[i].render(position);  
-    },
-    finish: function(position) {
-       for (var i = 0; i < this.effects.length; i++)
-          if(this.effects[i].finish) this.effects[i].finish(position);
-    }
-  });
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    for (var i = 0; i < this.effects.length; i++)
+      this.effects[i].render(position);
+  },
+  finish: function(position) {
+    for (var i = 0; i < this.effects.length; i++)
+      if(this.effects[i].finish) this.effects[i].finish(position);
+  }
+});
 
 // Internet Explorer caveat: works only on elements the have
-// a 'layout', meaning having a given width or height. 
+// a 'layout', meaning having a given width or height.
 // There is no way to safely set this automatically.
 Effect.Opacity = Class.create();
-Effect.Opacity.prototype = (new Effect.Base()).extend({
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
   initialize: function(element) {
     this.element = $(element);
-    options = {
+    options = Object.extend({
       from: 0.0,
       to:   1.0
-    }.extend(arguments[1] || {});
+    }, arguments[1] || {});
     this.start(options);
   },
   update: function(position) {
     this.setOpacity(position);
-  }, 
+  },
   setOpacity: function(opacity) {
     opacity = (opacity == 1) ? 0.99999 : opacity;
     this.element.style.opacity = opacity;
@@ -148,50 +166,49 @@
 });
 
 Effect.MoveBy = Class.create();
- Effect.MoveBy.prototype = (new Effect.Base()).extend({
-   initialize: function(element, toTop, toLeft) {
-     this.element      = $(element);
-     this.originalTop  = parseFloat(this.element.style.top || '0');
-     this.originalLeft = parseFloat(this.element.style.left || '0');
-     this.toTop        = toTop;
-     this.toLeft       = toLeft;
-     if(this.element.style.position == "")
-       this.element.style.position = "relative";
-     this.start(arguments[3]);
-   },
-   update: function(position) {
-     topd  = this.toTop  * position + this.originalTop;
-     leftd = this.toLeft * position + this.originalLeft;
-     this.setPosition(topd, leftd);
-   },
-   setPosition: function(topd, leftd) {
-     this.element.style.top  = topd  + "px";
-     this.element.style.left = leftd + "px";
-   }
+Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
+  initialize: function(element, toTop, toLeft) {
+    this.element      = $(element);
+    this.originalTop  = parseFloat(this.element.style.top || '0');
+    this.originalLeft = parseFloat(this.element.style.left || '0');
+    this.toTop        = toTop;
+    this.toLeft       = toLeft;
+    Element.makePositioned(this.element);
+    this.start(arguments[3]);
+  },
+  update: function(position) {
+    topd  = this.toTop  * position + this.originalTop;
+    leftd = this.toLeft * position + this.originalLeft;
+    this.setPosition(topd, leftd);
+  },
+  setPosition: function(topd, leftd) {
+    this.element.style.top  = topd  + "px";
+    this.element.style.left = leftd + "px";
+  }
 });
 
 Effect.Scale = Class.create();
-Effect.Scale.prototype = (new Effect.Base()).extend({
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
   initialize: function(element, percent) {
     this.element = $(element)
-    options = {
+    options = Object.extend({
       scaleX: true,
       scaleY: true,
       scaleContent: true,
       scaleFromCenter: false,
       scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
       scaleFrom: 100.0
-    }.extend(arguments[2] || {});
+    }, arguments[2] || {});
     this.originalTop    = this.element.offsetTop;
     this.originalLeft   = this.element.offsetLeft;
-    if (this.element.style.fontSize=="") this.sizeEm = 1.0;
-    if (this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0)
-       this.sizeEm      = parseFloat(this.element.style.fontSize);
+    if(this.element.style.fontSize=="") this.sizeEm = 1.0;
+    if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0)
+      this.sizeEm      = parseFloat(this.element.style.fontSize);
     this.factor = (percent/100.0) - (options.scaleFrom/100.0);
     if(options.scaleMode=='box') {
       this.originalHeight = this.element.clientHeight;
-      this.originalWidth  = this.element.clientWidth; 
-    } else 
+      this.originalWidth  = this.element.clientWidth;
+    } else
     if(options.scaleMode=='contents') {
       this.originalHeight = this.element.scrollHeight;
       this.originalWidth  = this.element.scrollWidth;
@@ -204,11 +221,11 @@
 
   update: function(position) {
     currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
-    if(this.options.scaleContent && this.sizeEm) 
+    if(this.options.scaleContent && this.sizeEm)
       this.element.style.fontSize = this.sizeEm*currentScale + "em";
     this.setDimensions(
-     this.originalWidth * currentScale, 
-     this.originalHeight * currentScale);
+      this.originalWidth * currentScale,
+      this.originalHeight * currentScale);
   },
 
   setDimensions: function(width, height) {
@@ -229,10 +246,10 @@
 });
 
 Effect.Highlight = Class.create();
-Effect.Highlight.prototype = (new Effect.Base()).extend({
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
   initialize: function(element) {
     this.element = $(element);
-    
+
     // try to parse current background color as default for endcolor
     // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format
     var endcolor = "#ffffff";
@@ -241,12 +258,13 @@
       endcolor = "#";
       var cols = current.slice(4,current.length-1).split(',');
       var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); }
-      
-    var options = {
-      startcolor: "#ffff99",
-      endcolor:   endcolor
-    }.extend(arguments[1] || {});
-    
+
+    var options = Object.extend({
+      startcolor:   "#ffff99",
+      endcolor:     endcolor,
+      restorecolor: current
+    }, arguments[1] || {});
+
     // init color calculations
     this.colors_base = [
       parseInt(options.startcolor.slice(1,3),16),
@@ -266,42 +284,66 @@
       Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ];
     this.element.style.backgroundColor = "#" +
       colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
+  },
+  finish: function() {
+    this.element.style.backgroundColor = this.options.restorecolor;
   }
 });
 
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    Position.prepare();
+    var offsets = Position.cumulativeOffset(this.element);
+    var max = window.innerHeight ?
+      window.height - window.innerHeight :
+      document.body.scrollHeight -
+        (document.documentElement.clientHeight ?
+          document.documentElement.clientHeight : document.body.clientHeight);
+    this.scrollStart = Position.deltaY;
+    this.delta  = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+    this.start(arguments[1] || {});
+  },
+  update: function(position) {
+    Position.prepare();
+    window.scrollTo(Position.deltaX,
+      this.scrollStart + (position*this.delta));
+  }
+});
 
 /* ------------- prepackaged effects ------------- */
 
-Effect.Fade =  function(element) {
-  options = {
+Effect.Fade = function(element) {
+  options = Object.extend({
   from: 1.0,
   to:   0.0,
-  afterFinish: function(effect) 
+  afterFinish: function(effect)
     { Element.hide(effect.element);
-      effect.setOpacity(1); } 
-  }.extend(arguments[1] || {});
+      effect.setOpacity(1); }
+  }, arguments[1] || {});
   new Effect.Opacity(element,options);
 }
 
-Effect.Appear =  function(element) {
-  options = {
+Effect.Appear = function(element) {
+  options = Object.extend({
   from: 0.0,
   to:   1.0,
-  beforeStart: function(effect)  
+  beforeStart: function(effect)
     { effect.setOpacity(0);
       Element.show(effect.element); },
-  afterUpdate: function(effect)  
+  afterUpdate: function(effect)
     { Element.show(effect.element); }
-  }.extend(arguments[1] || {});
+  }, arguments[1] || {});
   new Effect.Opacity(element,options);
 }
 
 Effect.Puff = function(element) {
   new Effect.Parallel(
-   [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), 
-     new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 
-     { duration: 1.0, 
-      afterUpdate: function(effect) 
+   [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),
+     new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
+     { duration: 1.0,
+      afterUpdate: function(effect)
        { effect.effects[0].element.style.position = 'absolute'; },
       afterFinish: function(effect)
        { Element.hide(effect.effects[0].element); }
@@ -310,34 +352,32 @@
 }
 
 Effect.BlindUp = function(element) {
-  $(element)._overflow = $(element).style.overflow || 'visible';
-  $(element).style.overflow = 'hidden';
-  new Effect.Scale(element, 0, 
-    { scaleContent: false, 
-      scaleX: false, 
-      afterFinish: function(effect) 
-        { 
+  Element.makeClipping(element);
+  new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false,
+      scaleX: false,
+      afterFinish: function(effect)
+        {
           Element.hide(effect.element);
-          effect.element.style.overflow = effect.element._overflow;
-        } 
-    }.extend(arguments[1] || {})
+          Element.undoClipping(effect.element);
+        }
+    }, arguments[1] || {})
   );
 }
 
 Effect.BlindDown = function(element) {
   $(element).style.height   = '0px';
-  $(element)._overflow = $(element).style.overflow || 'visible';
-  $(element).style.overflow = 'hidden';
+  Element.makeClipping(element);
   Element.show(element);
-  new Effect.Scale(element, 100, 
-    { scaleContent: false, 
-      scaleX: false, 
+  new Effect.Scale(element, 100,
+    Object.extend({ scaleContent: false,
+      scaleX: false,
       scaleMode: 'contents',
       scaleFrom: 0,
       afterFinish: function(effect) {
-        effect.element.style.overflow = effect.element._overflow;
+        Element.undoClipping(effect.element);
       }
-    }.extend(arguments[1] || {})
+    }, arguments[1] || {})
   );
 }
 
@@ -346,110 +386,112 @@
     { duration: 0.4,
      transition: Effect.Transitions.flicker,
      afterFinish: function(effect)
-      {  effect.element.style.overflow = 'hidden';
-        new Effect.Scale(effect.element, 1, 
+      { effect.element.style.overflow = 'hidden';
+        new Effect.Scale(effect.element, 1,
          { duration: 0.3, scaleFromCenter: true,
           scaleX: false, scaleContent: false,
-          afterUpdate: function(effect) { 
+          afterUpdate: function(effect) {
            if(effect.element.style.position=="")
              effect.element.style.position = 'relative'; },
           afterFinish: function(effect) { Element.hide(effect.element); }
          } )
       }
-    } )
+    } );
 }
 
 Effect.DropOut = function(element) {
   new Effect.Parallel(
-    [ new Effect.MoveBy(element, 100, 0, { sync: true }), 
-      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], 
-    { duration: 0.5, 
+    [ new Effect.MoveBy(element, 100, 0, { sync: true }),
+      new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
+    { duration: 0.5,
      afterFinish: function(effect)
-       { Element.hide(effect.effects[0].element); } 
+       { Element.hide(effect.effects[0].element); }
     });
 }
 
 Effect.Shake = function(element) {
-  new Effect.MoveBy(element, 0, 20, 
+  new Effect.MoveBy(element, 0, 20,
+    { duration: 0.05, afterFinish: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -40,
+    { duration: 0.1, afterFinish: function(effect) {
+  new Effect.MoveBy(effect.element, 0, 40,
+    { duration: 0.1, afterFinish: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -40,
+    { duration: 0.1, afterFinish: function(effect) {
+  new Effect.MoveBy(effect.element, 0, 40,
+    { duration: 0.1, afterFinish: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -20,
     { duration: 0.05, afterFinish: function(effect) {
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinish: function(effect) { 
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, -40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, 40, 
-    { duration: 0.1, afterFinish: function(effect) {  
-  new Effect.MoveBy(effect.element, 0, -20, 
-    { duration: 0.05, afterFinish: function(effect) {  
   }}) }}) }}) }}) }}) }});
 }
 
 Effect.SlideDown = function(element) {
-  $(element)._overflow = $(element).style.overflow || 'visible';
-  $(element).style.height   = '0px';
-  $(element).style.overflow = 'hidden';
-  $(element).firstChild.style.position = 'relative';
+  element = $(element);
+  element.style.height   = '0px';
+  Element.makeClipping(element);
+  Element.cleanWhitespace(element);
+  Element.makePositioned(element.firstChild);
   Element.show(element);
-  new Effect.Scale(element, 100, 
-   { scaleContent: false, 
-    scaleX: false, 
+  new Effect.Scale(element, 100,
+   Object.extend({ scaleContent: false,
+    scaleX: false,
     scaleMode: 'contents',
     scaleFrom: 0,
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
+    afterUpdate: function(effect)
+      { effect.element.firstChild.style.bottom =
           (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinish: function(effect) 
-      {  effect.element.style.overflow = effect.element._overflow; }
-    }.extend(arguments[1] || {})
+    afterFinish: function(effect)
+      {  Element.undoClipping(effect.element); }
+    }, arguments[1] || {})
   );
 }
-  
+
 Effect.SlideUp = function(element) {
-  $(element)._overflow = $(element).style.overflow || 'visible';
-  $(element).style.overflow = 'hidden';
-  $(element).firstChild.style.position = 'relative';
+  element = $(element);
+  Element.makeClipping(element);
+  Element.cleanWhitespace(element);
+  Element.makePositioned(element.firstChild);
   Element.show(element);
-  new Effect.Scale(element, 0, 
-   { scaleContent: false, 
-    scaleX: false, 
-    afterUpdate: function(effect) 
-      { effect.element.firstChild.style.bottom = 
+  new Effect.Scale(element, 0,
+   Object.extend({ scaleContent: false,
+    scaleX: false,
+    afterUpdate: function(effect)
+      { effect.element.firstChild.style.bottom =
           (effect.originalHeight - effect.element.clientHeight) + 'px'; },
     afterFinish: function(effect)
-      { 
+      {
         Element.hide(effect.element);
-        effect.element.style.overflow = effect.element._overflow; 
+        Element.undoClipping(effect.element);
       }
-   }.extend(arguments[1] || {})
+   }, arguments[1] || {})
   );
 }
 
 Effect.Squish = function(element) {
- new Effect.Scale(element, 0, 
+ new Effect.Scale(element, 0,
    { afterFinish: function(effect) { Element.hide(effect.element); } });
 }
 
 Effect.Grow = function(element) {
   element = $(element);
   var options = arguments[1] || {};
-  
+
   var originalWidth = element.clientWidth;
   var originalHeight = element.clientHeight;
   element.style.overflow = 'hidden';
   Element.show(element);
-  
+
   var direction = options.direction || 'center';
   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
   var opacityTransition = options.opacityTransition || Effect.Transitions.full;
-  
+
   var initialMoveX, initialMoveY;
   var moveX, moveY;
-  
+
   switch (direction) {
     case 'top-left':
-      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      initialMoveX = initialMoveY = moveX = moveY = 0;
       break;
     case 'top-right':
       initialMoveX = originalWidth;
@@ -474,16 +516,16 @@
       moveY = -originalHeight / 2;
       break;
   }
-  
-  new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
-    duration: 0.01, 
+
+  new Effect.MoveBy(element, initialMoveY, initialMoveX, {
+    duration: 0.01,
     beforeUpdate: function(effect) { $(element).style.height = '0px'; },
     afterFinish: function(effect) {
       new Effect.Parallel(
         [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
           new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
-          new Effect.Scale(element, 100, { 
-            scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, 
+          new Effect.Scale(element, 100, {
+            scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
             sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
         options); }
     });
@@ -492,7 +534,7 @@
 Effect.Shrink = function(element) {
   element = $(element);
   var options = arguments[1] || {};
-  
+
   var originalWidth = element.clientWidth;
   var originalHeight = element.clientHeight;
   element.style.overflow = 'hidden';
@@ -502,9 +544,9 @@
   var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
   var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
   var opacityTransition = options.opacityTransition || Effect.Transitions.none;
-  
+
   var moveX, moveY;
-  
+
   switch (direction) {
     case 'top-left':
       moveX = moveY = 0;
@@ -521,12 +563,12 @@
       moveX = originalWidth;
       moveY = originalHeight;
       break;
-    case 'center':  
+    case 'center':
       moveX = originalWidth / 2;
       moveY = originalHeight / 2;
       break;
   }
-  
+
   new Effect.Parallel(
     [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
       new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
@@ -539,32 +581,32 @@
   var transition = options.transition || Effect.Transitions.sinoidal;
   var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
   reverser.bind(transition);
-  new Effect.Opacity(element, 
-    {  duration: 3.0,
+  new Effect.Opacity(element,
+    Object.extend(Object.extend({  duration: 3.0,
        afterFinish: function(effect) { Element.show(effect.element); }
-    }.extend(options).extend({transition: reverser}));
+    }, options), {transition: reverser}));
 }
 
 Effect.Fold = function(element) {
  $(element).style.overflow = 'hidden';
- new Effect.Scale(element, 5, {   
+ new Effect.Scale(element, 5, Object.extend({
    scaleContent: false,
    scaleTo: 100,
    scaleX: false,
    afterFinish: function(effect) {
-   new Effect.Scale(element, 1, { 
-     scaleContent: false, 
+   new Effect.Scale(element, 1, {
+     scaleContent: false,
      scaleTo: 0,
      scaleY: false,
      afterFinish: function(effect) { Element.hide(effect.element) } });
- }}.extend(arguments[1] || {}));
+ }}, arguments[1] || {}));
 }
 
 // old: new Effect.ContentZoom(element, percent)
-// new: Element.setContentZoom(element, percent) 
+// new: Element.setContentZoom(element, percent)
 
 Element.setContentZoom = function(element, percent) {
   var element = $(element);
-  element.style.fontSize = (percent/100) + "em";  
+  element.style.fontSize = (percent/100) + "em";
   if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
 }