You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/05/21 18:14:04 UTC

svn commit: r540188 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/structure/ main/resources/org/apache/tapestry/ main/resources/org/apache/tapestry/corelib/comp...

Author: hlship
Date: Mon May 21 09:14:03 2007
New Revision: 540188

URL: http://svn.apache.org/viewvc?view=rev&rev=540188
Log:
TAPESTRY-1466: Support expansions inside ordinary attributes

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/AttributeExpansionBinding.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/StringProvider.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/AttributeExpansionsDemo.html
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.properties
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/AttributePageElement.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/BeanEditForm.html
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.html
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/default.css
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/templates.apt
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/unit-testing-pages.apt
    tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageElementFactoryImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/AttributeExpansionBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/AttributeExpansionBinding.java?view=auto&rev=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/AttributeExpansionBinding.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/AttributeExpansionBinding.java Mon May 21 09:14:03 2007
@@ -0,0 +1,40 @@
+// 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.
+
+package org.apache.tapestry.internal.services;
+
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.internal.bindings.AbstractBinding;
+import org.apache.tapestry.ioc.Location;
+
+/**
+ * Wraps a {@link StringProvider} as a read-only {@link Binding}.
+ */
+public class AttributeExpansionBinding extends AbstractBinding
+{
+    private final StringProvider _provider;
+
+    public AttributeExpansionBinding(StringProvider provider, Location location)
+    {
+        super(location);
+
+        _provider = provider;
+    }
+
+    public Object get()
+    {
+        return _provider.provideString();
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java Mon May 21 09:14:03 2007
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry.internal.services;
 
+import org.apache.tapestry.Binding;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.annotations.Component;
 import org.apache.tapestry.internal.parser.AttributeToken;
@@ -26,6 +27,7 @@
 import org.apache.tapestry.internal.structure.Page;
 import org.apache.tapestry.internal.structure.PageElement;
 import org.apache.tapestry.ioc.Location;
+import org.apache.tapestry.services.BindingSource;
 
 /**
  * Used by the {@link org.apache.tapestry.internal.services.PageLoader} to create page elements
@@ -36,11 +38,21 @@
 
     PageElement newStartElement(StartElementToken token);
 
-    PageElement newAttributeElement(AttributeToken token);
+    PageElement newAttributeElement(ComponentResources componentResources, AttributeToken token);
 
     PageElement newEndElement();
 
     PageElement newExpansionElement(ComponentResources componentResources, ExpansionToken token);
+
+    /**
+     * Creates a new binding as with
+     * {@link BindingSource#newBinding(String, ComponentResources, ComponentResources, String, String, Location)}.
+     * However, if the binding contains an expansion (i.e., <code>${...}</code>), then a binding
+     * that returns the fully expanded expression will be returned.
+     */
+    Binding newBinding(String parameterName, ComponentResources loadingComponentResources,
+            ComponentResources embeddedComponentResources, String defaultBindingPrefix,
+            String expression, Location location);
 
     /**
      * Creates a new component and adds it to the page and to its container.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java Mon May 21 09:14:03 2007
@@ -14,17 +14,20 @@
 
 package org.apache.tapestry.internal.services;
 
+import static org.apache.tapestry.TapestryConstants.PROP_BINDING_PREFIX;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
+
+import java.util.List;
+
 import org.apache.tapestry.Binding;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.TapestryConstants;
 import org.apache.tapestry.internal.parser.AttributeToken;
 import org.apache.tapestry.internal.parser.CommentToken;
 import org.apache.tapestry.internal.parser.DTDToken;
 import org.apache.tapestry.internal.parser.ExpansionToken;
 import org.apache.tapestry.internal.parser.StartElementToken;
 import org.apache.tapestry.internal.parser.TextToken;
-import org.apache.tapestry.internal.structure.AttributePageElement;
 import org.apache.tapestry.internal.structure.CommentPageElement;
 import org.apache.tapestry.internal.structure.ComponentPageElement;
 import org.apache.tapestry.internal.structure.ComponentPageElementImpl;
@@ -56,6 +59,23 @@
 
     private final ComponentMessagesSource _messagesSource;
 
+    private static final String EXPANSION_START = "${";
+
+    private static class LiteralStringProvider implements StringProvider
+    {
+        private final String _string;
+
+        LiteralStringProvider(String string)
+        {
+            _string = string;
+        }
+
+        public String provideString()
+        {
+            return _string;
+        }
+    }
+
     public PageElementFactoryImpl(ComponentInstantiatorSource componentInstantiatorSource,
             ComponentClassResolver resolver, TypeCoercer typeCoercer, BindingSource bindingSource,
             ComponentMessagesSource messagesSource)
@@ -97,9 +117,109 @@
         return _endElement;
     }
 
-    public PageElement newAttributeElement(AttributeToken token)
+    public PageElement newAttributeElement(ComponentResources componentResources,
+            final AttributeToken token)
     {
-        return new AttributePageElement(token.getName(), token.getValue());
+        final StringProvider provider = parseAttributeExpansionExpression(
+                token.getValue(),
+                componentResources,
+                token.getLocation());
+
+        final String name = token.getName();
+
+        return new PageElement()
+        {
+            public void render(MarkupWriter writer, RenderQueue queue)
+            {
+                writer.attributes(name, provider.provideString());
+            }
+        };
+    }
+
+    private StringProvider parseAttributeExpansionExpression(String expression,
+            ComponentResources resources, final Location location)
+    {
+        final List<StringProvider> providers = newList();
+
+        int startx = 0;
+
+        while (true)
+        {
+            int expansionx = expression.indexOf(EXPANSION_START, startx);
+
+            // No more expansions, add in the rest of the string as a literal.
+
+            if (expansionx < 0)
+            {
+                if (startx < expression.length())
+                    providers.add(new LiteralStringProvider(expression.substring(startx)));
+                break;
+            }
+
+            // Add in a literal string chunk for the characters between the last expansion and
+            // this expansion.
+
+            if (startx != expansionx)
+                providers.add(new LiteralStringProvider(expression.substring(startx, expansionx)));
+
+            int endx = expression.indexOf("}", expansionx);
+
+            if (endx < 0)
+                throw new TapestryException(ServicesMessages
+                        .unclosedAttributeExpression(expression), location, null);
+
+            String expansion = expression.substring(expansionx + 2, endx);
+
+            final Binding binding = _bindingSource.newBinding(
+                    "attribute expansion",
+                    resources,
+                    resources,
+                    PROP_BINDING_PREFIX,
+                    expansion,
+                    location);
+
+            final StringProvider provider = new StringProvider()
+            {
+                public String provideString()
+                {
+                    try
+                    {
+                        Object raw = binding.get();
+
+                        return _typeCoercer.coerce(raw, String.class);
+                    }
+                    catch (Exception ex)
+                    {
+                        throw new TapestryException(ex.getMessage(), location, ex);
+                    }
+                }
+            };
+
+            providers.add(provider);
+
+            // Restart the search after '}'
+
+            startx = endx + 1;
+        }
+
+        // Simplify the typical case, where the entire attribute is just a single expansion:
+
+        if (providers.size() == 1) return providers.get(0);
+
+        return new StringProvider()
+        {
+
+            public String provideString()
+            {
+                StringBuilder builder = new StringBuilder();
+
+                for (StringProvider provider : providers)
+                    builder.append(provider.provideString());
+
+                return builder.toString();
+            }
+        };
+
     }
 
     public PageElement newExpansionElement(ComponentResources componentResources,
@@ -109,7 +229,7 @@
                 "expansion",
                 componentResources,
                 componentResources,
-                TapestryConstants.PROP_BINDING_PREFIX,
+                PROP_BINDING_PREFIX,
                 token.getExpression(),
                 token.getLocation());
 
@@ -255,5 +375,29 @@
     public PageElement newDTDElement(DTDToken token)
     {
         return new DTDPageElement(token.getName(), token.getPublicId(), token.getSystemId());
+    }
+
+    public Binding newBinding(String parameterName, ComponentResources loadingComponentResources,
+            ComponentResources embeddedComponentResources, String defaultBindingPrefix,
+            String expression, Location location)
+    {
+
+        if (expression.contains(EXPANSION_START))
+        {
+            StringProvider provider = parseAttributeExpansionExpression(
+                    expression,
+                    loadingComponentResources,
+                    location);
+
+            return new AttributeExpansionBinding(provider, location);
+        }
+
+        return _bindingSource.newBinding(
+                "parameter " + parameterName,
+                loadingComponentResources,
+                embeddedComponentResources,
+                defaultBindingPrefix,
+                expression,
+                location);
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java Mon May 21 09:14:03 2007
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 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.
@@ -19,7 +19,6 @@
 import org.apache.tapestry.events.InvalidationListener;
 import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
 import org.apache.tapestry.internal.structure.Page;
-import org.apache.tapestry.services.BindingSource;
 import org.apache.tapestry.services.PersistentFieldManager;
 
 public class PageLoaderImpl extends InvalidationEventHubImpl implements PageLoader,
@@ -29,19 +28,17 @@
 
     private final PageElementFactory _pageElementFactory;
 
-    private final BindingSource _bindingSource;
-
     private final LinkFactory _linkFactory;
 
     private final PersistentFieldManager _persistentFieldManager;
 
     public PageLoaderImpl(ComponentTemplateSource templateSource,
-            PageElementFactory pageElementFactory, BindingSource bindingSource,
-            LinkFactory linkFactory, PersistentFieldManager persistentFieldManager)
+            PageElementFactory pageElementFactory, LinkFactory linkFactory,
+            PersistentFieldManager persistentFieldManager)
     {
         _templateSource = templateSource;
         _pageElementFactory = pageElementFactory;
-        _bindingSource = bindingSource;
+
         _linkFactory = linkFactory;
         _persistentFieldManager = persistentFieldManager;
     }
@@ -57,7 +54,7 @@
         // effort to pool them for reuse, but not too likely.
 
         PageLoaderProcessor processor = new PageLoaderProcessor(_templateSource,
-                _pageElementFactory, _bindingSource, _linkFactory, _persistentFieldManager);
+                _pageElementFactory, _linkFactory, _persistentFieldManager);
 
         return processor.loadPage(pageClassName, locale);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java Mon May 21 09:14:03 2007
@@ -79,8 +79,6 @@
 
     private boolean _dtdAdded;
 
-    private final BindingSource _bindingSource;
-
     private final Stack<BodyPageElement> _bodyPageElementStack = newStack();
 
     // You can use a stack as a queue
@@ -113,12 +111,11 @@
     private final ComponentTemplateSource _templateSource;
 
     public PageLoaderProcessor(ComponentTemplateSource templateSource,
-            PageElementFactory pageElementFactory, BindingSource bindingSource,
-            LinkFactory linkFactory, PersistentFieldManager persistentFieldManager)
+            PageElementFactory pageElementFactory, LinkFactory linkFactory,
+            PersistentFieldManager persistentFieldManager)
     {
         _templateSource = templateSource;
         _pageElementFactory = pageElementFactory;
-        _bindingSource = bindingSource;
         _linkFactory = linkFactory;
         _persistentFieldManager = persistentFieldManager;
     }
@@ -244,8 +241,8 @@
             return new InheritedBinding(description, existing, location);
         }
 
-        return _bindingSource.newBinding(
-                "parameter " + name,
+        return _pageElementFactory.newBinding(
+                name,
                 loadingComponent.getComponentResources(),
                 component.getComponentResources(),
                 defaultBindingPrefix,
@@ -295,7 +292,8 @@
             return;
         }
 
-        PageElement element = _pageElementFactory.newAttributeElement(token);
+        PageElement element = _pageElementFactory.newAttributeElement(_loadingElement
+                .getComponentResources(), token);
 
         addToBody(element);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Mon May 21 09:14:03 2007
@@ -388,4 +388,9 @@
     {
         return MESSAGES.get("corrupt-client-state");
     }
+
+    static String unclosedAttributeExpression(String expression)
+    {
+        return MESSAGES.format("unclosed-attribute-expression", expression);
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/StringProvider.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/StringProvider.java?view=auto&rev=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/StringProvider.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/StringProvider.java Mon May 21 09:14:03 2007
@@ -0,0 +1,27 @@
+// 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.
+
+package org.apache.tapestry.internal.services;
+
+/**
+ * Interface used when assembling an attribute value that contains expansions.
+ */
+public interface StringProvider
+{
+    /**
+     * Ask the object to provide the desired string. Often this involves computing the string value
+     * dynamically, or aggregating together multiple StringProviders.
+     */
+    String provideString();
+}
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/BeanEditForm.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/BeanEditForm.html?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/BeanEditForm.html (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/BeanEditForm.html Mon May 21 09:14:03 2007
@@ -7,7 +7,7 @@
             <t:delegate to="blockForProperty"/>
         </div>
         <div class="t-beaneditor-row">
-            <input t:type="Submit" type="submit" value="prop:submitLabel"/>
+            <input type="submit" value="${submitLabel}"/>
         </div>
     </div>
     

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.html?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.html (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/corelib/components/GridRows.html Mon May 21 09:14:03 2007
@@ -1,6 +1,6 @@
 <tr t:id="row" class="prop:rowClass" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
     <t:loop source="propertyNames" value="propertyName" volatile="true">
-        <td t:id="cell" class="prop:cellClass">
+        <td class="${cellClass}">
              <t:gridcell model="columnModel" row="row" resources="componentResources.containerResources"/>
         </td>        
     </t:loop>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/default.css
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/default.css?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/default.css (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/default.css Mon May 21 09:14:03 2007
@@ -271,7 +271,7 @@
 {
   width: 40px;
   text-align: right;
-  padding: none;
+  padding: 0px;
   background-color: #E1E1E1;
   padding-right: 3px; 
   border-right: 1px solid black;
@@ -292,4 +292,51 @@
 TD.t-location-content-first
 {
   border-top: 1px solid black;
+}
+
+DIV.t-palette {
+  display: inline;
+}
+
+DIV.t-palette SELECT {
+  margin-bottom: 2px;
+}
+
+DIV.t-palette-title {
+  color: white;
+  background-color: #809FFF;
+  text-align: center;
+  font-weight: bold;
+  margin-bottom: 3px;
+  display: block;
+}
+
+
+DIV.t-palette-available {
+  float: left;
+}
+
+DIV.t-palette-controls { 
+    margin: 0px 5px;
+	width: 64px;
+	float: left;
+	text-align: center;
+}
+
+DIV.t-palette-controls BUTTON { 
+  width: 64px;
+}
+
+DIV.t-palette-controls BUTTON[disabled] IMG  {
+  filter: alpha(opacity=25);
+  -moz-opacity: .25;
+}
+
+DIV.t-palette-selected {
+  float: left;
+  clear: right;
+}
+
+DIV.t-palette-spacer {
+  clear: left;
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Mon May 21 09:14:03 2007
@@ -76,4 +76,5 @@
 field-injection-error=Error obtaining injected value for field %s.%s: %s
 client-state-must-be-serializable=State persisted on the client must be serializable, but %s does not implement the Serializable interface.
 corrupt-client-state=Serialized client state was corrupted. \
-  This may indicate that too much state is being stored, which can cause the encoded string to be truncated by the client web browser.
\ No newline at end of file
+  This may indicate that too much state is being stored, which can cause the encoded string to be truncated by the client web browser.
+unclosed-attribute-expression=Attribute expression '%s' is missing a closing brace.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/templates.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/templates.apt?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/templates.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/templates.apt Mon May 21 09:14:03 2007
@@ -163,8 +163,15 @@
   Here, <<<$\{userId}>>> is the expansion.  In this example, the userId property of the 
   component is extracted, converted to a string, and streamed into the output.  
  
-  Expansions may only occur in ordinary template text; they are not allowed inside
-  attributes, or inside CDATA sections.
+  Expansions are allowed inside text, and inside attributes of ordinary elements, and component elements.  For example:
+  
++---+
+  <img src="${request.contextPath}/images/catalog/product_${productId}"/>
++---+
+
+  In this hypothetical example, the component class is providing a request property and a productId property, and these are being
+  used inside the template to assemble the src attribute of the \<img\> element.  This is component-like behavior without
+  actual components.
   
   Under the covers, expansions are the same as
   {{{parameters.html}parameter bindings}}.  The default

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/unit-testing-pages.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/unit-testing-pages.apt?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/unit-testing-pages.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/guide/unit-testing-pages.apt Mon May 21 09:14:03 2007
@@ -136,37 +136,4 @@
 
   To unit test a component, just create a test page containing that component. Then 
   unit test that page.
-  
-* Providing mock implementations for your service layer
 
-  Your page typically will invoke your business logic and will ultimately reach
-  the database. In unit testing, it is highly recommended that you not invoke
-  them as you're unit testing the UI (page). To do that, you should put 
-  your transaction control code, business logic and etc into Tapestry IoC
-  services. Then you should override their implementations using mock objects:
-
-+---+
-public class MyTest extends Assert
-{
-    @Test
-    public void test1()
-    {
-        String appPackage = "org.example.app";
-        String appName = "LocaleApp";
-        Map<String, Object> serviceOverrides = new HashMap<String, Object>();
-        Service1 service1Mock = new Service1() {
-            public Object transform(Object input) {
-                //assert against the input and return some mock value
-            }
-        }
-        Service2 service2Mock = new Service2() {
-            public void perform(Object cmd) {
-                //assert against the cmd
-            }
-        }
-        serviceOverrides.put("Service1", service1Mock);
-        serviceOverrides.put("Service2", service2Mock);
-        PageTester tester = new PageTester(appPackage, appName, "src/main/webapp", serviceOverrides);
-    }
-}
-+---+

Modified: tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/site/apt/index.apt Mon May 21 09:14:03 2007
@@ -13,6 +13,11 @@
   Progress on Tapestry 5 is really taking off. This space lists some cool new features that have been added
   recently.
   
+  * Template expansions are now allowed inside attributes.
+  
+  * Applications may optionally provide a global message catalog (which is searched when a page or component message catalog
+    can not directly provide a localized message).
+  
   * Explicit \<!DOCTYPE\> declarations inside page and component templates will now be forwarded through to the client
     web browser.
     

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/AttributeExpansionsDemo.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/AttributeExpansionsDemo.html?view=auto&rev=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/AttributeExpansionsDemo.html (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/AttributeExpansionsDemo.html Mon May 21 09:14:03 2007
@@ -0,0 +1,25 @@
+<t:border  xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+  
+  <h1>Expansions in Attributes</h1>
+  
+  <style>
+    DIV.red { font-weight: bold; color: red; }
+    
+    DIV.goober-red { font-style: italic; color: #FF9F2F; }
+ 
+    DIV.goober-green { font-size: x-large; color: green; }
+    
+  </style>
+  
+  <div id="mixed-expansion" style="color: ${colorscheme}">This text is blue, thanks to property colorscheme.</div>
+  
+  <div id="single" class="${styleClass}">This text is red and bold, thanks to the class attribute, set from property styleClass.</div>
+  
+  <div id="consecutive" class="${message:cssclassprefix}${styleClass}">And this text is italic and orange, from a complex CSS class attribute, built from multiple expansions.</div>
+  
+  <div id="trailer" class="${message:cssclassprefix}green">This text is green and large, and tests a trailing literal string within an attribute with an expansion.</div>
+  
+  <div id="formal"><t:echo value="${prefix}${message:alert}"/></div>
+  
+  <!-- A comment! -->
+</t:border>
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html Mon May 21 09:14:03 2007
@@ -134,6 +134,9 @@
           <li>
             <t:pagelink page="ClientPersistenceDemo">Client Persistence Demo</t:pagelink> -- component field values persisted on the client side
           </li>
+          <li>
+            <t:pagelink page="attributeExpansionsDemo">Attribute Expansions Demo</t:pagelink> -- use expansions inside attributes of ordinary elements
+          </li>
         </ul>
       </td>
     </tr>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Mon May 21 09:14:03 2007
@@ -563,7 +563,7 @@
     @Test
     public void bean_editor()
     {
-        String submitButton = "//input[@id='submit']";
+        String submitButton = "//input[@type='submit']";
 
         open(BASE_URL);
         clickAndWait("link=BeanEditor Demo");
@@ -582,7 +582,7 @@
 
         // Check override of the submit label
 
-        assertText("//input[@id='submit']/@value", "Register");
+        assertText("//input[@type='submit']/@value", "Register");
 
         type("firstName", "a");
         type("lastName", "b");
@@ -819,7 +819,7 @@
 
         // Notice: click, not click and wait.
 
-        click("submit");
+        click("//input[@type='submit']");
 
         assertTextSeries(
                 "//li[%d]",
@@ -831,14 +831,14 @@
         type("firstName", "Howard");
         type("lastName", "Lewis Ship");
         type("birthYear", "1000");
-        click("submit");
+        click("//input[@type='submit']");
 
         assertText("//li", "Year of Birth requires a value of at least 1900.");
 
         type("birthYear", "1966");
         click("citizen");
 
-        clickAndWait("submit");
+        clickAndWait("//input[@type='submit']");
 
         assertTextPresent("First Name: [Howard]");
     }
@@ -925,5 +925,24 @@
         clickAndWait("link=store string");
 
         assertTextPresent("Persisted value: [A String]", "Session: [false]");
+    }
+
+    @Test
+    public void attribute_expansions()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Attribute Expansions Demo");
+
+        assertText("//div[@id='mixed-expansion']/@style", "color: blue;");
+        assertText("//div[@id='single']/@class", "red");
+        assertText("//div[@id='consecutive']/@class", "goober-red");
+        assertText("//div[@id='trailer']/@class", "goober-green");
+        assertText(
+                "//div[@id='formal']/text()",
+                "ALERT-expansions work inside formal component parameters as well");
+
+        // An unrelated test, but fills in a bunch of minor gaps.
+
+        assertSourcePresent("<!-- A comment! -->");
     }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.java?view=auto&rev=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.java Mon May 21 09:14:03 2007
@@ -0,0 +1,33 @@
+// 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.
+
+package org.apache.tapestry.integration.app1.pages;
+
+public class AttributeExpansionsDemo
+{
+    public String getColorScheme()
+    {
+        return "blue";
+    }
+
+    public String getStyleClass()
+    {
+        return "red";
+    }
+
+    public String getPrefix()
+    {
+        return "ALERT-";
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageElementFactoryImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageElementFactoryImplTest.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageElementFactoryImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageElementFactoryImplTest.java Mon May 21 09:14:03 2007
@@ -12,166 +12,204 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
-import org.apache.tapestry.MarkupWriter;
-import org.apache.tapestry.dom.MarkupModel;
-import org.apache.tapestry.dom.XMLMarkupModel;
-import org.apache.tapestry.internal.parser.AttributeToken;
-import org.apache.tapestry.internal.parser.StartElementToken;
-import org.apache.tapestry.internal.parser.TextToken;
-import org.apache.tapestry.internal.structure.ComponentPageElement;
-import org.apache.tapestry.internal.structure.PageElement;
-import org.apache.tapestry.internal.test.InternalBaseTestCase;
+package org.apache.tapestry.internal.services;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.dom.MarkupModel;
+import org.apache.tapestry.dom.XMLMarkupModel;
+import org.apache.tapestry.internal.parser.AttributeToken;
+import org.apache.tapestry.internal.parser.StartElementToken;
+import org.apache.tapestry.internal.parser.TextToken;
+import org.apache.tapestry.internal.structure.ComponentPageElement;
+import org.apache.tapestry.internal.structure.PageElement;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
 import org.apache.tapestry.ioc.Location;
-import org.apache.tapestry.runtime.RenderQueue;
-import org.apache.tapestry.services.ComponentClassResolver;
-import org.testng.annotations.Test;
-
-public class PageElementFactoryImplTest extends InternalBaseTestCase
-{
-    private static MarkupModel _xmlModel = new XMLMarkupModel();
-
-    @Test
-    public void start_element()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-        MarkupWriter writer = new MarkupWriterImpl();
-        Location l = mockLocation();
-        RenderQueue queue = mockRenderQueue();
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-        StartElementToken token = new StartElementToken("fred", l);
-
-        PageElement element = factory.newStartElement(token);
-
-        element.render(writer, queue);
-
-        verify();
-
-        assertEquals(element.toString(), "Start[fred]");
-        assertEquals(writer.toString(), "<fred></fred>");
-    }
-
-    @Test
-    public void attribute()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-        MarkupWriter writer = new MarkupWriterImpl(_xmlModel, null);
-        Location l = mockLocation();
-        RenderQueue queue = mockRenderQueue();
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-        AttributeToken token = new AttributeToken("name", "value", l);
-
-        PageElement element = factory.newAttributeElement(token);
-
-        writer.element("root");
-
-        element.render(writer, queue);
-
-        verify();
-
-        assertEquals(element.toString(), "Attribute[name=value]");
-        assertEquals(writer.toString(), "<root name=\"value\"/>");
-    }
-
-    @Test
-    public void end_element()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-        MarkupWriter writer = new MarkupWriterImpl(_xmlModel, null);
-        RenderQueue queue = mockRenderQueue();
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-
-        PageElement element = factory.newEndElement();
-
-        writer.element("root");
-        writer.write("before");
-        writer.element("nested");
-
-        element.render(writer, queue);
-
-        writer.write("after");
-
-        verify();
-
-        assertEquals(element.toString(), "End");
-        assertEquals(writer.toString(), "<root>before<nested/>after</root>");
-    }
-
-    @Test
-    public void end_element_is_singleton()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-
-        PageElement element1 = factory.newEndElement();
-        PageElement element2 = factory.newEndElement();
-
-        assertSame(element2, element1);
-
-        verify();
-    }
-
-    @Test
-    public void text_element()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-        MarkupWriter writer = new MarkupWriterImpl();
-        Location l = mockLocation();
-        RenderQueue queue = mockRenderQueue();
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-        TextToken token = new TextToken("some text", l);
-
-        PageElement element = factory.newTextElement(token);
-
-        writer.element("root");
-        element.render(writer, queue);
-
-        verify();
-
-        assertEquals(element.toString(), "Text[some text]");
-        assertEquals(writer.toString(), "<root>some text</root>");
-    }
-
-    @Test
-    public void render_body_element()
-    {
-        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
-        ComponentClassResolver resolver = mockComponentClassResolver();
-        RenderQueue queue = mockRenderQueue();
-        ComponentPageElement component = newMock(ComponentPageElement.class);
-        MarkupWriter writer = newMock(MarkupWriter.class);
-
-        component.enqueueBeforeRenderBody(queue);
-
-        replay();
-
-        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
-
-        PageElement element = factory.newRenderBodyElement(component);
-
-        element.render(writer, queue);
-
-        verify();
-    }
-}
+import org.apache.tapestry.ioc.internal.util.TapestryException;
+import org.apache.tapestry.ioc.services.TypeCoercer;
+import org.apache.tapestry.runtime.RenderQueue;
+import org.apache.tapestry.services.BindingSource;
+import org.apache.tapestry.services.ComponentClassResolver;
+import org.apache.tapestry.services.ComponentMessagesSource;
+import org.testng.annotations.Test;
+
+public class PageElementFactoryImplTest extends InternalBaseTestCase
+{
+    private static MarkupModel _xmlModel = new XMLMarkupModel();
+
+    @Test
+    public void start_element()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        MarkupWriter writer = new MarkupWriterImpl();
+        Location l = mockLocation();
+        RenderQueue queue = mockRenderQueue();
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+        StartElementToken token = new StartElementToken("fred", l);
+
+        PageElement element = factory.newStartElement(token);
+
+        element.render(writer, queue);
+
+        verify();
+
+        assertEquals(element.toString(), "Start[fred]");
+        assertEquals(writer.toString(), "<fred></fred>");
+    }
+
+    @Test
+    public void attribute()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        MarkupWriter writer = new MarkupWriterImpl(_xmlModel, null);
+        Location l = mockLocation();
+        RenderQueue queue = mockRenderQueue();
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+        AttributeToken token = new AttributeToken("name", "value", l);
+
+        PageElement element = factory.newAttributeElement(null, token);
+
+        writer.element("root");
+
+        element.render(writer, queue);
+
+        verify();
+
+        assertEquals(writer.toString(), "<root name=\"value\"/>");
+    }
+
+    @Test
+    public void end_element()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        MarkupWriter writer = new MarkupWriterImpl(_xmlModel, null);
+        RenderQueue queue = mockRenderQueue();
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+
+        PageElement element = factory.newEndElement();
+
+        writer.element("root");
+        writer.write("before");
+        writer.element("nested");
+
+        element.render(writer, queue);
+
+        writer.write("after");
+
+        verify();
+
+        assertEquals(element.toString(), "End");
+        assertEquals(writer.toString(), "<root>before<nested/>after</root>");
+    }
+
+    @Test
+    public void end_element_is_singleton()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+
+        PageElement element1 = factory.newEndElement();
+        PageElement element2 = factory.newEndElement();
+
+        assertSame(element2, element1);
+
+        verify();
+    }
+
+    @Test
+    public void text_element()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        MarkupWriter writer = new MarkupWriterImpl();
+        Location l = mockLocation();
+        RenderQueue queue = mockRenderQueue();
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+        TextToken token = new TextToken("some text", l);
+
+        PageElement element = factory.newTextElement(token);
+
+        writer.element("root");
+        element.render(writer, queue);
+
+        verify();
+
+        assertEquals(element.toString(), "Text[some text]");
+        assertEquals(writer.toString(), "<root>some text</root>");
+    }
+
+    @Test
+    public void render_body_element()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        RenderQueue queue = mockRenderQueue();
+        ComponentPageElement component = mockComponentPageElement();
+        MarkupWriter writer = newMock(MarkupWriter.class);
+
+        component.enqueueBeforeRenderBody(queue);
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, null, null, null);
+
+        PageElement element = factory.newRenderBodyElement(component);
+
+        element.render(writer, queue);
+
+        verify();
+    }
+
+    @Test
+    public void unclosed_attribute_expression()
+    {
+        ComponentInstantiatorSource source = mockComponentInstantiatorSource();
+        ComponentClassResolver resolver = mockComponentClassResolver();
+        TypeCoercer typeCoercer = mockTypeCoercer();
+        BindingSource bindingSource = mockBindingSource();
+        ComponentMessagesSource messagesSource = newMock(ComponentMessagesSource.class);
+        ComponentResources resources = mockComponentResources();
+        Location location = mockLocation();
+
+        AttributeToken token = new AttributeToken("fred", "${flintstone", location);
+
+        replay();
+
+        PageElementFactory factory = new PageElementFactoryImpl(source, resolver, typeCoercer,
+                bindingSource, messagesSource);
+
+        try
+        {
+            factory.newAttributeElement(resources, token);
+            unreachable();
+        }
+        catch (TapestryException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Attribute expression \'${flintstone\' is missing a closing brace.");
+            assertSame(ex.getLocation(), location);
+        }
+
+        verify();
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java?view=diff&rev=540188&r1=540187&r2=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java Mon May 21 09:14:03 2007
@@ -28,7 +28,6 @@
 import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.model.EmbeddedComponentModel;
-import org.apache.tapestry.services.BindingSource;
 import org.testng.annotations.Test;
 
 public class PageLoaderImplTest extends InternalBaseTestCase
@@ -44,7 +43,6 @@
     {
         ComponentTemplateSource templateSource = mockComponentTemplateSource();
         PageElementFactory elementFactory = mockPageElementFactory();
-        BindingSource bindingSource = mockBindingSource();
         ComponentPageElement rootElement = mockComponentPageElement();
         InternalComponentResources resources = mockInternalComponentResources();
         ComponentModel model = mockComponentModel();
@@ -76,8 +74,7 @@
 
         replay();
 
-        PageLoader loader = new PageLoaderImpl(templateSource, elementFactory, bindingSource, null,
-                null);
+        PageLoader loader = new PageLoaderImpl(templateSource, elementFactory, null, null);
 
         loader.loadPage(PAGE_CLASS_NAME, LOCALE);
 
@@ -89,7 +86,6 @@
     {
         ComponentTemplateSource templateSource = mockComponentTemplateSource();
         PageElementFactory elementFactory = mockPageElementFactory();
-        BindingSource bindingSource = mockBindingSource();
         ComponentPageElement rootElement = mockComponentPageElement();
         InternalComponentResources resources = mockInternalComponentResources();
         ComponentModel model = mockComponentModel();
@@ -163,8 +159,7 @@
 
         replay();
 
-        PageLoader loader = new PageLoaderImpl(templateSource, elementFactory, bindingSource, null,
-                null);
+        PageLoader loader = new PageLoaderImpl(templateSource, elementFactory, null, null);
 
         loader.loadPage(PAGE_CLASS_NAME, LOCALE);
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.properties?view=auto&rev=540188
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.properties (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/AttributeExpansionsDemo.properties Mon May 21 09:14:03 2007
@@ -0,0 +1,16 @@
+# 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.
+
+cssClassPrefix=goober-
+alert=expansions work inside formal component parameters as well
\ No newline at end of file