tapestry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jkuhn...@apache.org
Subject svn commit: r543957 [2/5] - in /tapestry/tapestry4/trunk: ./ src/site/ src/site/resources/images/ComponentReference/ src/site/xdoc/components/dojo/ src/site/xdoc/components/general/ src/site/xdoc/components/link/ src/site/xdoc/components/scriptaculous/...
Date Sun, 03 Jun 2007 18:51:11 GMT
Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java Sun Jun  3 11:51:08 2007
@@ -0,0 +1,349 @@
+package org.apache.tapestry.scriptaculous;
+
+import org.apache.hivemind.ApplicationRuntimeException;
+import org.apache.hivemind.util.Defense;
+import org.apache.tapestry.*;
+import org.apache.tapestry.coerce.ValueConverter;
+import org.apache.tapestry.engine.DirectServiceParameter;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.form.AbstractFormComponent;
+import org.apache.tapestry.form.TranslatedField;
+import org.apache.tapestry.form.TranslatedFieldSupport;
+import org.apache.tapestry.form.ValidatableFieldSupport;
+import org.apache.tapestry.json.JSONObject;
+import org.apache.tapestry.link.DirectLink;
+import org.apache.tapestry.listener.ListenerInvoker;
+import org.apache.tapestry.services.ResponseBuilder;
+import org.apache.tapestry.util.SizeRestrictingIterator;
+import org.apache.tapestry.valid.ValidatorException;
+
+import java.text.ParseException;
+import java.util.*;
+
+/**
+ * Implementation of the <a href="http://wiki.script.aculo.us/scriptaculous/show/Ajax.Autocompleter">Ajax.Autocompleter</a> in
+ * the form of a {@link org.apache.tapestry.form.TextField} like component with the additional ability to dynamically suggest
+ * values via XHR requests.
+ *
+ * <p>
+ * This component will use the html element tag name defined in your html template to include it to determine whether or not
+ * to render a TextArea or TextField style input element. For example, specifying a component definition such as:
+ * </p>
+ *
+ * <pre>&lt;input jwcid="@Suggest" value="literal:A default value" /&gt;</pre>
+ *
+ * <p>
+ * would render something looking like:
+ * </p>
+ *
+ * <pre>&lt;input type="text" name="suggest" id="suggest" autocomplete="off" value="literal:A default value" /&gt;</pre>
+ *
+ * <p>while a defintion of</p>
+ *
+ * <pre>&lt;textarea jwcid="@Suggest" value="literal:A default value" /&gt;</pre>
+ *
+ * <p>would render something like:</p>
+ *
+ * <pre>
+ *  &lt;textarea name="suggest" id="suggest" &gt;A default value&lt;textarea/&gt;
+ * </pre>
+ *
+ */
+public abstract class Suggest extends AbstractFormComponent implements TranslatedField, IDirect {
+
+    /**
+     * Injected service used to invoke whatever listeners people have setup to handle
+     * changing value from this field.
+     *
+     * @return The invoker.
+     */
+    public abstract ListenerInvoker getListenerInvoker();
+
+    /**
+     * Injected response builder for doing specific XHR things.
+     *
+     * @return ResponseBuilder for this request. 
+     */
+    public abstract ResponseBuilder getResponse();
+
+    /**
+     * Associated javascript template.
+     *
+     * @return The script template.
+     */
+    public abstract IScript getScript();
+
+    /**
+     * Used to convert form input values.
+     *
+     * @return The value converter to use.
+     */
+    public abstract ValueConverter getValueConverter();
+    
+    /**
+     * Injected.
+     *
+     * @return Service used to validate input.
+     */
+    public abstract ValidatableFieldSupport getValidatableFieldSupport();
+
+    /**
+     * Injected.
+     *
+     * @return Translation service.
+     */
+    public abstract TranslatedFieldSupport getTranslatedFieldSupport();
+
+    /**
+     * Injected.
+     *
+     * @return The {@link org.apache.tapestry.engine.DirectService} engine.  
+     */
+    public abstract IEngineService getEngineService();
+
+    ////////////////////////////////////////////////////////
+    // Parameters
+    ////////////////////////////////////////////////////////
+
+    public abstract Object getValue();
+    public abstract void setValue(Object value);
+
+    public abstract ListItemRenderer getListItemRenderer();
+    public abstract void setListItemRenderer(ListItemRenderer renderer);
+
+    public abstract IActionListener getListener();
+
+    public abstract Object getListSource();
+    public abstract void setListSource(Object value);
+
+    public abstract int getMaxResults();
+
+    public abstract Object getParameters();
+
+    public abstract String getOptions();
+
+    public abstract String getUpdateElementClass();
+
+    /**
+     * Used internally to track listener invoked searches versus
+     * normal rendering requests.
+     *
+     * @return True if search was triggered, false otherwise.
+     */
+    public abstract boolean isSearchTriggered();
+    public abstract void setSearchTriggered(boolean value);
+
+    public boolean isRequired()
+    {
+        return getValidatableFieldSupport().isRequired(this);
+    }
+
+    protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        // render search triggered response instead of normal render if
+        // listener was invoked
+
+        if (!cycle.isRewinding()
+            && getResponse().isDynamic() && isSearchTriggered()) {
+
+            IForm form = TapestryUtils.getForm(cycle, this);
+            
+            if (form.wasPrerendered(writer, this))
+                return;
+
+            renderList(writer, cycle);
+            return;
+        }
+
+        // defer to super if normal render
+        super.renderComponent(writer, cycle);
+    }
+
+    /**
+     * Invoked only when a search has been triggered to render out the &lt;li&gt; list of
+     * dynamic suggestion options.
+     *
+     * @param writer
+     *          The markup writer.
+     * @param cycle
+     *          The associated request.
+     */
+    public void renderList(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        Defense.notNull(getListSource(), "listSource for Suggest component.");
+
+        Iterator values = (Iterator)getValueConverter().coerceValue(getListSource(), Iterator.class);
+        
+        if (isParameterBound("maxResults"))
+        {
+            values = new SizeRestrictingIterator(values, getMaxResults());
+        }
+        
+        getListItemRenderer().renderList(writer, cycle, values);
+    }
+
+    protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        String value = getTranslatedFieldSupport().format(this, getValue());
+        boolean isTextArea = getTemplateTagName().equalsIgnoreCase("textarea");
+
+        renderDelegatePrefix(writer, cycle);
+
+        if (isTextArea)
+            writer.begin(getTemplateTagName());
+        else
+            writer.beginEmpty(getTemplateTagName());
+
+        // only render input attributes if not a textarea
+        if (!isTextArea)
+        {
+            writer.attribute("type", "text");
+            writer.attribute("autocomplete", "off");
+        }
+
+        renderIdAttribute(writer, cycle);
+        writer.attribute("name", getName());
+
+        if (isDisabled())
+            writer.attribute("disabled", "disabled");
+
+        renderInformalParameters(writer, cycle);
+        renderDelegateAttributes(writer, cycle);
+
+        getTranslatedFieldSupport().renderContributions(this, writer, cycle);
+        getValidatableFieldSupport().renderContributions(this, writer, cycle);
+
+        if (value != null)
+        {
+            if (!isTextArea)
+                writer.attribute("value", value);
+            else
+                writer.print(value);
+        }
+
+        if (!isTextArea)
+            writer.closeTag();
+        else
+            writer.end();
+
+        renderDelegateSuffix(writer, cycle);
+
+        // render update element
+
+        writer.begin("div");
+        writer.attribute("id", getClientId() + "choices");
+        writer.attribute("class", getUpdateElementClass());
+        writer.end();
+
+        // render javascript
+
+        JSONObject json = null;
+        String options = getOptions();
+        
+        try {
+
+            json = options != null ? new JSONObject(options) : new JSONObject();
+            
+        } catch (ParseException ex)
+        {
+            throw new ApplicationRuntimeException(ScriptaculousMessages.invalidOptions(options, ex), this.getBinding("options").getLocation(), ex);
+        }
+
+        // bind onFailure client side function if not already defined
+        if (!json.has("onFailure"))
+        {
+            json.put("onFailure", "tapestry.error");
+        }
+
+        Map parms = new HashMap();
+        parms.put("inputId", getClientId());
+        parms.put("updateId", getClientId() + "choices");
+        parms.put("options", json.toString());
+
+        Object[] specifiedParams = DirectLink.constructServiceParameters(getParameters());
+        Object[] listenerParams = null;
+        if (specifiedParams != null)
+        {
+            listenerParams = new Object[specifiedParams.length + 1];
+            System.arraycopy(specifiedParams, 0, listenerParams, 1, specifiedParams.length);
+        } else {
+
+            listenerParams = new Object[1];
+        }
+        
+        listenerParams[0] = getClientId();
+
+        ILink updateLink = getEngineService().getLink(isStateful(), new DirectServiceParameter(this, listenerParams));
+        parms.put("updateUrl", updateLink.getURL());
+
+        PageRenderSupport pageRenderSupport = TapestryUtils.getPageRenderSupport(cycle, this);
+        getScript().execute(this, cycle, pageRenderSupport, parms);
+    }
+
+    /**
+     * Rewinds the component, doing translation, validation and binding.
+     */
+    protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        String value = cycle.getParameter(getName());
+        try
+        {
+            Object object = getTranslatedFieldSupport().parse(this, value);
+            getValidatableFieldSupport().validate(this, writer, cycle, object);
+
+            setValue(object);
+        } catch (ValidatorException e)
+        {
+            getForm().getDelegate().recordFieldInputValue(value);
+            getForm().getDelegate().record(e);
+        }
+    }
+
+    /**
+     * Triggers the listener. The parameters passed are the current text
+     * and those specified in the parameters parameter of the component.
+     */
+    public void trigger(IRequestCycle cycle)
+    {
+        IActionListener listener = getListener();
+        if (listener == null)
+            throw Tapestry.createRequiredParameterException(this, "listener");
+
+        Object[] params = cycle.getListenerParameters();
+
+        // replace the first param with the correct value
+        String inputId = (String)params[0];
+        params[0] = cycle.getParameter(inputId);
+
+        cycle.setListenerParameters(params);
+
+        setSearchTriggered(true);
+
+        getListenerInvoker().invokeListener(listener, this, cycle);
+    }
+
+    public List getUpdateComponents()
+    {
+        return Arrays.asList(new Object[] { getClientId() });
+    }
+
+    public boolean isAsync()
+    {
+        return true;
+    }
+
+    public boolean isJson()
+    {
+        return false;
+    }
+
+    /**
+     * Sets the default {@link ListItemRenderer} for component, to be overriden as
+     * necessary by component parameters.
+     */
+    protected void finishLoad()
+    {
+        setListItemRenderer(DefaultListItemRenderer.SHARED_INSTANCE);
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.jwc
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.jwc?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.jwc (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.jwc Sun Jun  3 11:51:08 2007
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Copyright 2007 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.
+-->
+        
+<!DOCTYPE component-specification PUBLIC
+        "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
+        "http://tapestry.apache.org/dtd/Tapestry_4_0.dtd">
+
+<component-specification class="org.apache.tapestry.scriptaculous.Suggest" allow-body="no" allow-informal-parameters="yes">
+
+    <description>
+        Provides dynamic suggestion list autocompletion for textarea and input text fields.
+    </description>
+
+    <parameter name="listener" required="true">
+        <description>
+            Listener method to invoke for each field search request. This listener
+            should expect to get exactly one paramter of type String, which is the value that was typed in to the field. It may additionally
+            receive extra parameters if the parameters parameter is used.
+        </description>
+    </parameter>
+
+    <parameter name="parameters">
+        <description>
+            An array of objects to be encoded into the URL.
+            These parameters will be decoded when the autocompleter is triggered
+            and passed to the listener method. Note that the first parameter passed
+            to the listener will always be the 'search string' - the parameters
+            defined here will follow.
+        </description>
+    </parameter>
+
+    <parameter name="listSource" required="true">
+        <description>
+            Object,String[],Collection of values to be used to autocomplete a particular
+            autocomplete field search, this should be set when this component invokes your input
+            search listener.
+        </description>
+    </parameter>
+
+    <parameter name="stateful" default-value="ognl:false">
+        <description>
+            Whether or not the request created by this component should be required to be stateful or not, default is false.
+        </description>
+    </parameter>
+
+    <parameter name="listItemRenderer">
+        <description>
+            The ListItemRenderer that should be used to render the drop down list, the
+            default renderer iterates over the values and puts the string value in a &lt;li&gt;&lt;/li&gt; block.
+        </description>
+    </parameter>
+
+    <parameter name="maxResults">
+        <description>The maximum number of results to display in response to an autocompletion search request.</description>
+    </parameter>
+
+    <parameter name="updateElementClass" default-value="literal:autocomplete">
+        <description>
+            The class attribute set on the element that will be populated with the results of the autocompletion list
+            response.
+        </description>
+    </parameter>
+    
+    <parameter name="options" default-value="literal:{method: 'get', frequency: 0.2}">
+        <description>
+            Options to be passed directly to the javascript constructor of this javascript control in the form
+            of a json object property map.
+        </description>
+    </parameter>
+
+    <parameter name="value" required="yes" />
+    <parameter name="disabled" />
+    <parameter name="displayName" />
+    <parameter name="translator" default-value="translator:string" />
+    <parameter name="validators" />
+
+    <inject property="listenerInvoker" object="infrastructure:listenerInvoker" />
+    <inject property="engineService" object="service:tapestry.services.Direct" />    
+    <inject property="script" type="script" object="Suggest.script" />
+    <inject property="valueConverter" object="service:tapestry.coerce.ValueConverter" />
+    <inject property="translatedFieldSupport" object="service:tapestry.form.TranslatedFieldSupport" />
+    <inject property="validatableFieldSupport" object="service:tapestry.form.ValidatableFieldSupport" />
+
+</component-specification>

Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.script
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.script?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.script (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/scriptaculous/Suggest.script Sun Jun  3 11:51:08 2007
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!--
+   Copyright 2007 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.
+-->
+
+<!DOCTYPE script PUBLIC
+        "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+        "http://tapestry.apache.org/dtd/Script_3_0.dtd">
+
+<script>
+    <include-script resource-path="/scriptaculous-1.7.1/prototype.js" />
+    <include-script resource-path="/scriptaculous-1.7.1/effects.js" />
+    <include-script resource-path="/scriptaculous-1.7.1/controls.js" />
+
+    <input-symbol key="updateUrl" required="yes" />
+    <input-symbol key="inputId" required="yes" />
+    <input-symbol key="updateId" required="yes" />
+    <input-symbol key="options" required="yes" />
+
+    <let key="completer" unique="yes">${inputId}</let>
+
+    <initialization>
+        var ${completer} = new Ajax.Autocompleter("${inputId}", "${updateId}", "${updateUrl}", ${options});
+    </initialization>
+</script>

Modified: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/ResponseBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/ResponseBuilder.java?view=diff&rev=543957&r1=543956&r2=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/ResponseBuilder.java (original)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/ResponseBuilder.java Sun Jun  3 11:51:08 2007
@@ -13,15 +13,11 @@
 // limitations under the License.
 package org.apache.tapestry.services;
 
-import java.io.IOException;
-
-import org.apache.tapestry.IComponent;
-import org.apache.tapestry.IMarkupWriter;
-import org.apache.tapestry.IRender;
-import org.apache.tapestry.IRequestCycle;
-import org.apache.tapestry.PageRenderSupport;
+import org.apache.tapestry.*;
 import org.apache.tapestry.services.impl.DojoAjaxResponseBuilder;
 
+import java.io.IOException;
+
 /**
  * Represents the service responsible for managing all content output that is sent
  * to the client. In the case of normal http responses this management would inlude 
@@ -47,7 +43,6 @@
  * when managaing client side browser state. 
  * </p>
  *
- * @author jkuhnert
  * @since 4.1
  */
 public interface ResponseBuilder extends PageRenderSupport {
@@ -325,5 +320,5 @@
      * @param text
      *          The status message. 
      */
-    void addStatusMessage(IMarkupWriter normalWriter, String category, String text);
+    void addStatusMessage(IMarkupWriter writer, String category, String text);
 }

Modified: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/JSONResponseContributorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/JSONResponseContributorImpl.java?view=diff&rev=543957&r1=543956&r2=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/JSONResponseContributorImpl.java (original)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/JSONResponseContributorImpl.java Sun Jun  3 11:51:08 2007
@@ -13,8 +13,6 @@
 // limitations under the License.
 package org.apache.tapestry.services.impl;
 
-import java.io.IOException;
-
 import org.apache.tapestry.IRequestCycle;
 import org.apache.tapestry.asset.AssetFactory;
 import org.apache.tapestry.markup.MarkupWriterSource;
@@ -23,6 +21,8 @@
 import org.apache.tapestry.services.ResponseContributor;
 import org.apache.tapestry.web.WebRequest;
 import org.apache.tapestry.web.WebResponse;
+
+import java.io.IOException;
 
 /**
  * Determines if incoming request is a valid json request via the "json" http header

Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java Sun Jun  3 11:51:08 2007
@@ -0,0 +1,552 @@
+package org.apache.tapestry.services.impl;
+
+import org.apache.hivemind.Resource;
+import org.apache.hivemind.util.Defense;
+import org.apache.tapestry.*;
+import org.apache.tapestry.asset.AssetFactory;
+import org.apache.tapestry.engine.NullWriter;
+import org.apache.tapestry.markup.MarkupWriterSource;
+import org.apache.tapestry.markup.NestedMarkupWriterImpl;
+import org.apache.tapestry.services.RequestLocaleManager;
+import org.apache.tapestry.services.ResponseBuilder;
+import org.apache.tapestry.services.ServiceConstants;
+import org.apache.tapestry.util.ContentType;
+import org.apache.tapestry.util.PageRenderSupportImpl;
+import org.apache.tapestry.web.WebResponse;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.*;
+
+/**
+ * Implementation of response builder for prototype client side library initiated XHR requests.
+ * 
+ */
+public class PrototypeResponseBuilder implements ResponseBuilder {
+
+    public static final String CONTENT_TYPE = "text/html";
+
+    private final AssetFactory _assetFactory;
+
+    private final String _namespace;
+
+    private PageRenderSupportImpl _prs;
+
+    // used to create IMarkupWriter
+    private RequestLocaleManager _localeManager;
+    private MarkupWriterSource _markupWriterSource;
+    private WebResponse _response;
+
+    // our response writer
+    private IMarkupWriter _writer;
+
+    // Parts that will be updated.
+    private List _parts = new ArrayList();
+
+    // Map of specialized writers, like scripts
+
+    private Map _writers = new HashMap();
+    private IRequestCycle _cycle;
+
+    /**
+     * Used for unit testing only.
+     *
+     * @param cycle Request.
+     * @param writer Markup writer.
+     * @param parts Update parts list.
+     */
+    public PrototypeResponseBuilder(IRequestCycle cycle, IMarkupWriter writer, List parts)
+    {
+        _cycle = cycle;
+        _writer = writer;
+        
+        if (parts != null)
+            _parts.addAll(parts);
+
+        _assetFactory = null;
+        _namespace = null;
+    }
+
+    /**
+     * Creates a new response builder with the required services it needs
+     * to render the response when {@link #renderResponse(IRequestCycle)} is called.
+     * 
+     * @param cycle
+     *          Associated request.
+     * @param localeManager
+     *          Locale manager to use for response.
+     * @param markupWriterSource
+     *          Creates necessary {@link IMarkupWriter} instances.
+     * @param webResponse
+     *          The http response.
+     * @param assetFactory
+     *          Asset manager for script / other resource inclusion.
+     * @param namespace
+     *          Javascript namespace value - used in portlets.
+     */
+    public PrototypeResponseBuilder(IRequestCycle cycle,
+            RequestLocaleManager localeManager,
+            MarkupWriterSource markupWriterSource,
+            WebResponse webResponse,
+            AssetFactory assetFactory, String namespace)
+    {
+        Defense.notNull(cycle, "cycle");
+        Defense.notNull(assetFactory, "assetService");
+
+        _cycle = cycle;
+        _localeManager = localeManager;
+        _markupWriterSource = markupWriterSource;
+        _response = webResponse;
+
+        // Used by PageRenderSupport
+
+        _assetFactory = assetFactory;
+        _namespace = namespace;
+    }
+
+    /**
+     *
+     * {@inheritDoc}
+     */
+    public boolean isDynamic()
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void renderResponse(IRequestCycle cycle)
+        throws IOException
+    {
+        _localeManager.persistLocale();
+
+        ContentType contentType = new ContentType(CONTENT_TYPE + ";charset=" + cycle.getInfrastructure().getOutputEncoding());
+
+        String encoding = contentType.getParameter(ENCODING_KEY);
+
+        if (encoding == null)
+        {
+            encoding = cycle.getEngine().getOutputEncoding();
+
+            contentType.setParameter(ENCODING_KEY, encoding);
+        }
+
+        if (_writer == null) {
+
+            parseParameters(cycle);
+
+            PrintWriter printWriter = _response.getPrintWriter(contentType);
+
+            _writer = _markupWriterSource.newMarkupWriter(printWriter, contentType);
+        }
+
+        // render response
+
+        _prs = new PageRenderSupportImpl(_assetFactory, _namespace, cycle.getPage().getLocation(), this);
+
+        TapestryUtils.storePageRenderSupport(cycle, _prs);
+
+        cycle.renderPage(this);
+
+        TapestryUtils.removePageRenderSupport(cycle);
+
+        endResponse();
+
+        _writer.close();
+    }
+
+    public void flush()
+    throws IOException
+    {
+        _writer.flush();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updateComponent(String id)
+    {
+        if (!_parts.contains(id))
+            _parts.add(id);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public IMarkupWriter getWriter()
+    {
+        return _writer;
+    }
+
+    void setWriter(IMarkupWriter writer)
+    {
+        _writer = writer;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isBodyScriptAllowed(IComponent target)
+    {
+        if (target != null
+                && IPage.class.isInstance(target)
+                || (IForm.class.isInstance(target)
+                && ((IForm)target).isFormFieldUpdating()))
+            return true;
+
+        return contains(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isExternalScriptAllowed(IComponent target)
+    {
+        if (target != null
+                && IPage.class.isInstance(target)
+                || (IForm.class.isInstance(target)
+                && ((IForm)target).isFormFieldUpdating()))
+            return true;
+
+        return contains(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isInitializationScriptAllowed(IComponent target)
+    {
+        if (target != null
+                && IPage.class.isInstance(target)
+                || (IForm.class.isInstance(target)
+                && ((IForm)target).isFormFieldUpdating()))
+            return true;
+
+        return contains(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isImageInitializationAllowed(IComponent target)
+    {
+        if (target != null
+                && IPage.class.isInstance(target)
+                || (IForm.class.isInstance(target)
+                && ((IForm)target).isFormFieldUpdating()))
+            return true;
+
+        return contains(target);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getPreloadedImageReference(IComponent target, IAsset source)
+    {
+        return _prs.getPreloadedImageReference(target, source);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getPreloadedImageReference(IComponent target, String url)
+    {
+        return _prs.getPreloadedImageReference(target, url);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getPreloadedImageReference(String url)
+    {
+        return _prs.getPreloadedImageReference(url);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addBodyScript(IComponent target, String script)
+    {
+        _prs.addBodyScript(target, script);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addBodyScript(String script)
+    {
+        _prs.addBodyScript(script);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addExternalScript(IComponent target, Resource resource)
+    {
+        _prs.addExternalScript(target, resource);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addExternalScript(Resource resource)
+    {
+        _prs.addExternalScript(resource);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addInitializationScript(IComponent target, String script)
+    {
+        _prs.addInitializationScript(target, script);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void addInitializationScript(String script)
+    {
+        _prs.addInitializationScript(script);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getUniqueString(String baseValue)
+    {
+        return _prs.getUniqueString(baseValue);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle)
+    {
+        _prs.writeBodyScript(writer, cycle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeInitializationScript(IMarkupWriter writer)
+    {
+        _prs.writeInitializationScript(writer);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void beginBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
+    {
+        _writer.begin("script");
+        _writer.printRaw("\n//<![CDATA[\n");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void endBodyScript(IMarkupWriter normalWriter, IRequestCycle cycle)
+    {
+        _writer.printRaw("\n//]]>\n");
+        _writer.end();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeBodyScript(IMarkupWriter normalWriter, String script, IRequestCycle cycle)
+    {
+        _writer.printRaw(script);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeExternalScript(IMarkupWriter normalWriter, String url, IRequestCycle cycle)
+    {
+        _writer.begin("script");
+        _writer.attribute("type", "text/javascript");
+        _writer.attribute("src", url);
+        _writer.end();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeImageInitializations(IMarkupWriter normalWriter, String script, String preloadName, IRequestCycle cycle)
+    {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void writeInitializationScript(IMarkupWriter normalWriter, String script)
+    {
+        _writer.begin("script");
+
+        // return is in XML so must escape any potentially non-xml compliant content
+        _writer.printRaw("\n//<![CDATA[\n");
+        _writer.printRaw(script);
+        _writer.printRaw("\n//]]>\n");
+        _writer.end();
+    }
+
+    public void addStatus(IMarkupWriter normalWriter, String text)
+    {
+        throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
+    }
+
+    public void addStatusMessage(IMarkupWriter normalWriter, String category, String text)
+    {
+        throw new UnsupportedOperationException("Can't return a status response with prototype based requests.");
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void render(IMarkupWriter writer, IRender render, IRequestCycle cycle)
+    {
+        // must be a valid writer already
+
+        if (NestedMarkupWriterImpl.class.isInstance(writer)) {
+            render.render(writer, cycle);
+            return;
+        }
+
+        if (IComponent.class.isInstance(render)
+            && contains((IComponent)render, ((IComponent)render).peekClientId()))
+        {
+            render.render(getComponentWriter( ((IComponent)render).peekClientId() ), cycle);
+            return;
+        }
+
+        // Nothing else found, throw out response
+
+        render.render(NullWriter.getSharedInstance(), cycle);
+    }
+
+    IMarkupWriter getComponentWriter(String id)
+    {
+        return getWriter(id, ELEMENT_TYPE);
+    }
+
+    /**
+     *
+     * {@inheritDoc}
+     */
+    public IMarkupWriter getWriter(String id, String type)
+    {
+        Defense.notNull(id, "id can't be null");
+        
+        IMarkupWriter w = (IMarkupWriter)_writers.get(id);
+        if (w != null)
+            return w;
+        
+        IMarkupWriter nestedWriter = _writer.getNestedWriter();
+        _writers.put(id, nestedWriter);
+
+        return nestedWriter;
+    }
+
+    void beginResponse()
+    {
+    }
+
+    /**
+     * Invoked to clear out tempoary partial writer buffers before rendering exception
+     * page.
+     */
+    void clearPartialWriters()
+    {
+        _writers.clear();
+    }
+
+    /**
+     * Called after the entire response has been captured. Causes
+     * the writer buffer output captured to be segmented and written
+     * out to the right response elements for the client libraries to parse.
+     */
+    void endResponse()
+    {
+        Iterator keys = _writers.keySet().iterator();
+
+        while (keys.hasNext()) {
+
+            String key = (String)keys.next();
+            NestedMarkupWriter nw = (NestedMarkupWriter)_writers.get(key);
+            
+            nw.close();
+        }
+        
+        _writer.flush();
+    }
+
+    /**
+     * Grabs the incoming parameters needed for json responses, most notable the
+     * {@link ServiceConstants#UPDATE_PARTS} parameter.
+     *
+     * @param cycle
+     *            The request cycle to parse from
+     */
+    void parseParameters(IRequestCycle cycle)
+    {
+        Object[] updateParts = cycle.getParameters(ServiceConstants.UPDATE_PARTS);
+
+        if (updateParts == null)
+            return;
+
+        for(int i = 0; i < updateParts.length; i++)
+        {
+            _parts.add(updateParts[i].toString());
+        }
+    }
+
+    /**
+     * Determines if the specified component is contained in the
+     * responses requested update parts.
+     * @param target
+     *          The component to check for.
+     * @return True if the request should capture the components output.
+     */
+    public boolean contains(IComponent target)
+    {
+        if (target == null)
+            return false;
+
+        String id = target.getClientId();
+
+        return contains(target, id);
+    }
+
+    boolean contains(IComponent target, String id)
+    {
+        if (_parts.contains(id))
+            return true;
+
+        Iterator it = _cycle.renderStackIterator();
+        while (it.hasNext()) {
+
+            IComponent comp = (IComponent)it.next();
+            String compId = comp.getClientId();
+
+            if (comp != target && _parts.contains(compId))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean explicitlyContains(IComponent target)
+    {
+        if (target == null)
+            return false;
+
+        return _parts.contains(target.getId());
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseBuilder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java Sun Jun  3 11:51:08 2007
@@ -0,0 +1,69 @@
+package org.apache.tapestry.services.impl;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.asset.AssetFactory;
+import org.apache.tapestry.markup.MarkupWriterSource;
+import org.apache.tapestry.services.RequestLocaleManager;
+import org.apache.tapestry.services.ResponseBuilder;
+import org.apache.tapestry.services.ResponseContributor;
+import org.apache.tapestry.web.WebRequest;
+import org.apache.tapestry.web.WebResponse;
+
+import java.io.IOException;
+
+/**
+ * Implementation of prototype javascript library XHR request sniffer.
+ */
+public class PrototypeResponseContributorImpl implements ResponseContributor {
+
+    public static final String PROTOTYPE_HEADER = "X-Prototype-Version";
+
+    private RequestLocaleManager _localeManager;
+    private MarkupWriterSource _markupWriterSource;
+    private WebResponse _webResponse;
+    private WebRequest _webRequest;
+    private AssetFactory _assetFactory;
+
+    /**
+     * {@inheritDoc}
+     */
+    public ResponseBuilder createBuilder(IRequestCycle cycle)
+            throws IOException
+    {
+        return new PrototypeResponseBuilder(cycle, _localeManager, _markupWriterSource,
+                                            _webResponse, _assetFactory, _webResponse.getNamespace());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean handlesResponse(IRequestCycle cycle)
+    {
+        return _webRequest.getHeader(PROTOTYPE_HEADER) != null;
+    }
+
+    public void setLocaleManager(RequestLocaleManager localeManager)
+    {
+        _localeManager = localeManager;
+    }
+
+    public void setMarkupWriterSource(MarkupWriterSource markupWriterSource)
+    {
+        _markupWriterSource = markupWriterSource;
+    }
+
+    public void setWebResponse(WebResponse webResponse)
+    {
+        _webResponse = webResponse;
+    }
+
+    public void setWebRequest(WebRequest webRequest)
+    {
+        _webRequest  = webRequest;
+    }
+
+    public void setAssetFactory(AssetFactory factory)
+    {
+        _assetFactory = factory;
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/services/impl/PrototypeResponseContributorImpl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java Sun Jun  3 11:51:08 2007
@@ -0,0 +1,89 @@
+package org.apache.tapestry.util;
+
+import org.apache.hivemind.util.Defense;
+
+import java.util.Iterator;
+
+/**
+ * <p>This class implements an {@link Iterator} which can only return a fixed
+ * number of items.</p>
+ *
+ */
+public class SizeRestrictingIterator implements Iterator {
+
+    private static final int DEFAULT_MAX_SIZE = 20;
+
+    private final int _maxSize;
+    private final Iterator _iterator;
+    private int _currentIndex;
+
+    /**
+     * Constructs an Iterator which will return at most {@link #DEFAULT_MAX_SIZE} items.
+     *
+     * @param iterator
+     *          The underlying iterator this object will defer to for actual
+     *          iteration.
+     */
+    public SizeRestrictingIterator(Iterator iterator)
+    {
+        this(iterator, DEFAULT_MAX_SIZE);
+    }
+
+    /**
+     * Constructs an Iterator which will return at most as many
+     * items as defined by the user.
+     *
+     * @param iterator
+     *          The underlying iterator this object will defer to for actual
+     *          iteration.
+     * @param maxSize
+     *          How many items to return / filter the list by.
+     */
+    public SizeRestrictingIterator(Iterator iterator, int maxSize)
+    {
+        Defense.notNull(iterator, "Iterator source");
+        
+        _iterator = iterator;
+        _maxSize = maxSize;
+        
+        _currentIndex = 0;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+        return _currentIndex < _maxSize && _iterator.hasNext();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object next()
+    {
+        _currentIndex++;
+        return _iterator.next();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+        _currentIndex--;
+        _iterator.remove();
+    }
+    
+    public String toString()
+    {
+        return "SizeRestrictingIterator[" +
+               "_maxSize=" + _maxSize +
+               '\n' +
+               ", _current=" + _currentIndex +
+               '\n' +
+               ", _iterator=" + _iterator +
+               '\n' +
+               ']';
+    }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/java/org/apache/tapestry/util/SizeRestrictingIterator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/MIT-LICENSE
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/MIT-LICENSE?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/MIT-LICENSE (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/MIT-LICENSE Sun Jun  3 11:51:08 2007
@@ -0,0 +1,20 @@
+Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+
+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
+without limitation the rights to use, copy, modify, merge, publish,
+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
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js Sun Jun  3 11:51:08 2007
@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/builder.js
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js?view=auto&rev=543957
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js Sun Jun  3 11:51:08 2007
@@ -0,0 +1,875 @@
+// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// 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.getToken(), 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: [',', '\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.
+
+if(typeof Effect == 'undefined')
+  throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+  baseInitialize: function(element, update, options) {
+    element          = $(element)
+    this.element     = element; 
+    this.update      = $(update);  
+    this.hasFocus    = false; 
+    this.changed     = false; 
+    this.active      = false; 
+    this.index       = 0;     
+    this.entryCount  = 0;
+
+    if(this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || {};
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow || 
+      function(element, update){ 
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false, 
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
+    this.options.onHide = this.options.onHide || 
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if(typeof(this.options.tokens) == 'string') 
+      this.options.tokens = new Array(this.options.tokens);
+
+    this.observer = null;
+    
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
+
+    // Turn autocomplete back on when the user leaves the page, so that the
+    // field's value will be remembered on Mozilla-based browsers.
+    Event.observe(window, 'beforeunload', function(){ 
+      element.setAttribute('autocomplete', 'on'); 
+    });
+  },
+
+  show: function() {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix && 
+      (Prototype.Browser.IE) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update, 
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         if(Prototype.Browser.WebKit) Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         if(Prototype.Browser.WebKit) Event.stop(event);
+         return;
+      }
+     else 
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer = 
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    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.selectEntry();
+    this.hide();
+  },
+  
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;     
+  }, 
+  
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ? 
+          Element.addClassName(this.getEntry(i),"selected") : 
+          Element.removeClassName(this.getEntry(i),"selected");
+      if(this.hasFocus) { 
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+  
+  markPrevious: function() {
+    if(this.index > 0) this.index--
+      else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
+  },
+  
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++
+      else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
+  },
+  
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+  
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+  
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+    var value = '';
+    if (this.options.select) {
+      var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var lastTokenPos = this.findLastToken();
+    if (lastTokenPos != -1) {
+      var newValue = this.element.value.substr(0, lastTokenPos + 1);
+      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value;
+    } else {
+      this.element.value = value;
+    }
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.down());
+
+      if(this.update.firstChild && this.update.down().childNodes) {
+        this.entryCount = 
+          this.update.down().childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else { 
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+      this.index = 0;
+      
+      if(this.entryCount==1 && this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        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.getToken().length>=this.options.minChars) {
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+
+  getToken: function() {
+    var tokenPos = this.findLastToken();
+    if (tokenPos != -1)
+      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+    else
+      var ret = this.element.value;
+
+    return /\n/.test(ret) ? '' : ret;
+  },
+
+  findLastToken: function() {
+    var lastTokenPos = -1;
+
+    for (var i=0; i<this.options.tokens.length; i++) {
+      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+      if (thisTokenPos > lastTokenPos)
+        lastTokenPos = thisTokenPos;
+    }
+    return lastTokenPos;
+  }
+}
+
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+  initialize: function(element, update, url, options) {
+    this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    this.startIndicator();
+    
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams) 
+      this.options.parameters += '&' + this.options.defaultParams;
+    
+    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
+//
+// - partialSearch - 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 fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - 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.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        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 foundPos = instance.options.ignoreCase ? 
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) { 
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars && 
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || {});
+  }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = $(element);
+
+    this.options = Object.extend({
+      paramName: "value",
+      okButton: true,
+      okLink: false,
+      okText: "ok",
+      cancelButton: false,
+      cancelLink: true,
+      cancelText: "cancel",
+      textBeforeControls: '',
+      textBetweenControls: '',
+      textAfterControls: '',
+      savingText: "Saving...",
+      clickToEditText: "Click to edit",
+      okText: "ok",
+      rows: 1,
+      onComplete: function(transport, element) {
+        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+      },
+      onFailure: function(transport) {
+        alert("Error communicating with the server: " + transport.responseText.stripTags());
+      },
+      callback: function(form) {
+        return Form.serialize(form);
+      },
+      handleLineBreaks: true,
+      loadingText: 'Loading...',
+      savingClassName: 'inplaceeditor-saving',
+      loadingClassName: 'inplaceeditor-loading',
+      formClassName: 'inplaceeditor-form',
+      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+      highlightendcolor: "#FFFFFF",
+      externalControl: null,
+      submitOnBlur: false,
+      ajaxOptions: {},
+      evalScripts: false
+    }, options || {});
+
+    if(!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + "-inplaceeditor";
+      if ($(this.options.formId)) {
+        // there's already a form with that name, don't specify an id
+        this.options.formId = null;
+      }
+    }
+    
+    if (this.options.externalControl) {
+      this.options.externalControl = $(this.options.externalControl);
+    }
+    
+    this.originalBackground = Element.getStyle(this.element, 'background-color');
+    if (!this.originalBackground) {
+      this.originalBackground = "transparent";
+    }
+    
+    this.element.title = this.options.clickToEditText;
+    
+    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+    Event.observe(this.element, 'click', this.onclickListener);
+    Event.observe(this.element, 'mouseover', this.mouseoverListener);
+    Event.observe(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.observe(this.options.externalControl, 'click', this.onclickListener);
+      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  },
+  enterEditMode: function(evt) {
+    if (this.saving) return;
+    if (this.editing) return;
+    this.editing = true;
+    this.onEnterEditMode();
+    if (this.options.externalControl) {
+      Element.hide(this.options.externalControl);
+    }
+    Element.hide(this.element);
+    this.createForm();
+    this.element.parentNode.insertBefore(this.form, this.element);
+    if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
+    // stop the event to avoid a page refresh in Safari
+    if (evt) {
+      Event.stop(evt);
+    }
+    return false;
+  },
+  createForm: function() {
+    this.form = document.createElement("form");
+    this.form.id = this.options.formId;
+    Element.addClassName(this.form, this.options.formClassName)
+    this.form.onsubmit = this.onSubmit.bind(this);
+
+    this.createEditField();
+
+    if (this.options.textarea) {
+      var br = document.createElement("br");
+      this.form.appendChild(br);
+    }
+    
+    if (this.options.textBeforeControls)
+      this.form.appendChild(document.createTextNode(this.options.textBeforeControls));
+
+    if (this.options.okButton) {
+      var okButton = document.createElement("input");
+      okButton.type = "submit";
+      okButton.value = this.options.okText;
+      okButton.className = 'editor_ok_button';
+      this.form.appendChild(okButton);
+    }
+    
+    if (this.options.okLink) {
+      var okLink = document.createElement("a");
+      okLink.href = "#";
+      okLink.appendChild(document.createTextNode(this.options.okText));
+      okLink.onclick = this.onSubmit.bind(this);
+      okLink.className = 'editor_ok_link';
+      this.form.appendChild(okLink);
+    }
+    
+    if (this.options.textBetweenControls && 
+      (this.options.okLink || this.options.okButton) && 
+      (this.options.cancelLink || this.options.cancelButton))
+      this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
+      
+    if (this.options.cancelButton) {
+      var cancelButton = document.createElement("input");
+      cancelButton.type = "submit";
+      cancelButton.value = this.options.cancelText;
+      cancelButton.onclick = this.onclickCancel.bind(this);
+      cancelButton.className = 'editor_cancel_button';
+      this.form.appendChild(cancelButton);
+    }
+
+    if (this.options.cancelLink) {
+      var cancelLink = document.createElement("a");
+      cancelLink.href = "#";
+      cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+      cancelLink.onclick = this.onclickCancel.bind(this);
+      cancelLink.className = 'editor_cancel editor_cancel_link';      
+      this.form.appendChild(cancelLink);
+    }
+    
+    if (this.options.textAfterControls)
+      this.form.appendChild(document.createTextNode(this.options.textAfterControls));
+  },
+  hasHTMLLineBreaks: function(string) {
+    if (!this.options.handleLineBreaks) return false;
+    return string.match(/<br/i) || string.match(/<p>/i);
+  },
+  convertHTMLLineBreaks: function(string) {
+    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+  },
+  createEditField: function() {
+    var text;
+    if(this.options.loadTextURL) {
+      text = this.options.loadingText;
+    } else {
+      text = this.getText();
+    }
+
+    var obj = this;
+    
+    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+      this.options.textarea = false;
+      var textField = document.createElement("input");
+      textField.obj = this;
+      textField.type = "text";
+      textField.name = this.options.paramName;
+      textField.value = text;
+      textField.style.backgroundColor = this.options.highlightcolor;
+      textField.className = 'editor_field';
+      var size = this.options.size || this.options.cols || 0;
+      if (size != 0) textField.size = size;
+      if (this.options.submitOnBlur)
+        textField.onblur = this.onSubmit.bind(this);
+      this.editField = textField;
+    } else {
+      this.options.textarea = true;
+      var textArea = document.createElement("textarea");
+      textArea.obj = this;
+      textArea.name = this.options.paramName;
+      textArea.value = this.convertHTMLLineBreaks(text);
+      textArea.rows = this.options.rows;
+      textArea.cols = this.options.cols || 40;
+      textArea.className = 'editor_field';      
+      if (this.options.submitOnBlur)
+        textArea.onblur = this.onSubmit.bind(this);
+      this.editField = textArea;
+    }
+    
+    if(this.options.loadTextURL) {
+      this.loadExternalText();
+    }
+    this.form.appendChild(this.editField);
+  },
+  getText: function() {
+    return this.element.innerHTML;
+  },
+  loadExternalText: function() {
+    Element.addClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = true;
+    new Ajax.Request(
+      this.options.loadTextURL,
+      Object.extend({
+        asynchronous: true,
+        onComplete: this.onLoadedExternalText.bind(this)
+      }, this.options.ajaxOptions)
+    );
+  },
+  onLoadedExternalText: function(transport) {
+    Element.removeClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = false;
+    this.editField.value = transport.responseText.stripTags();
+    Field.scrollFreeActivate(this.editField);
+  },
+  onclickCancel: function() {
+    this.onComplete();
+    this.leaveEditMode();
+    return false;
+  },
+  onFailure: function(transport) {
+    this.options.onFailure(transport);
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+      this.oldInnerHTML = null;
+    }
+    return false;
+  },
+  onSubmit: function() {
+    // onLoading resets these so we need to save them away for the Ajax call
+    var form = this.form;
+    var value = this.editField.value;
+    
+    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+    // to be displayed indefinitely
+    this.onLoading();
+    
+    if (this.options.evalScripts) {
+      new Ajax.Request(
+        this.url, Object.extend({
+          parameters: this.options.callback(form, value),
+          onComplete: this.onComplete.bind(this),
+          onFailure: this.onFailure.bind(this),
+          asynchronous:true, 
+          evalScripts:true
+        }, this.options.ajaxOptions));
+    } else  {
+      new Ajax.Updater(
+        { success: this.element,
+          // don't update on failure (this could be an option)
+          failure: null }, 
+        this.url, Object.extend({
+          parameters: this.options.callback(form, value),
+          onComplete: this.onComplete.bind(this),
+          onFailure: this.onFailure.bind(this)
+        }, this.options.ajaxOptions));
+    }
+    // stop the event to avoid a page refresh in Safari
+    if (arguments.length > 1) {
+      Event.stop(arguments[0]);
+    }
+    return false;
+  },
+  onLoading: function() {
+    this.saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  showSaving: function() {
+    this.oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    Element.addClassName(this.element, this.options.savingClassName);
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+  },
+  removeForm: function() {
+    if(this.form) {
+      if (this.form.parentNode) Element.remove(this.form);
+      this.form = null;
+    }
+  },
+  enterHover: function() {
+    if (this.saving) return;
+    this.element.style.backgroundColor = this.options.highlightcolor;
+    if (this.effect) {
+      this.effect.cancel();
+    }
+    Element.addClassName(this.element, this.options.hoverClassName)
+  },
+  leaveHover: function() {
+    if (this.options.backgroundColor) {
+      this.element.style.backgroundColor = this.oldBackground;
+    }
+    Element.removeClassName(this.element, this.options.hoverClassName)
+    if (this.saving) return;
+    this.effect = new Effect.Highlight(this.element, {
+      startcolor: this.options.highlightcolor,
+      endcolor: this.options.highlightendcolor,
+      restorecolor: this.originalBackground
+    });
+  },
+  leaveEditMode: function() {
+    Element.removeClassName(this.element, this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+    if (this.options.externalControl) {
+      Element.show(this.options.externalControl);
+    }
+    this.editing = false;
+    this.saving = false;
+    this.oldInnerHTML = null;
+    this.onLeaveEditMode();
+  },
+  onComplete: function(transport) {
+    this.leaveEditMode();
+    this.options.onComplete.bind(this)(transport, this.element);
+  },
+  onEnterEditMode: function() {},
+  onLeaveEditMode: function() {},
+  dispose: function() {
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+    }
+    this.leaveEditMode();
+    Event.stopObserving(this.element, 'click', this.onclickListener);
+    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
+    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
+      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  }
+};
+
+Ajax.InPlaceCollectionEditor = Class.create();
+Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
+Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
+  createEditField: function() {
+    if (!this.cached_selectTag) {
+      var selectTag = document.createElement("select");
+      var collection = this.options.collection || [];
+      var optionTag;
+      collection.each(function(e,i) {
+        optionTag = document.createElement("option");
+        optionTag.value = (e instanceof Array) ? e[0] : e;
+        if((typeof this.options.value == 'undefined') && 
+          ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
+        if(this.options.value==optionTag.value) optionTag.selected = true;
+        optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
+        selectTag.appendChild(optionTag);
+      }.bind(this));
+      this.cached_selectTag = selectTag;
+    }
+
+    this.editField = this.cached_selectTag;
+    if(this.options.loadTextURL) this.loadExternalText();
+    this.form.appendChild(this.editField);
+    this.options.callback = function(form, value) {
+      return "value=" + encodeURIComponent(value);
+    }
+  }
+});
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create();
+Form.Element.DelayedObserver.prototype = {
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+};

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/scriptaculous-1.7.1/controls.js
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message