Change-Id: Id4f119b22d44f6abf63e730442e22a34e7c1953ftags/7.6.0.alpha2
@@ -79,6 +79,7 @@ import com.vaadin.shared.AbstractComponentState; | |||
import com.vaadin.shared.EventId; | |||
import com.vaadin.shared.ui.ComponentStateUtil; | |||
import com.vaadin.shared.ui.combobox.FilteringMode; | |||
import com.vaadin.shared.util.SharedUtil; | |||
/** | |||
* Client side implementation of the Select component. | |||
@@ -98,7 +99,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
private final String key; | |||
private final String caption; | |||
private String iconUri; | |||
private String untranslatedIconUri; | |||
/** | |||
* Constructor | |||
@@ -110,8 +111,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
key = uidl.getStringAttribute("key"); | |||
caption = uidl.getStringAttribute("caption"); | |||
if (uidl.hasAttribute("icon")) { | |||
iconUri = client.translateVaadinUri(uidl | |||
.getStringAttribute("icon")); | |||
untranslatedIconUri = uidl.getStringAttribute("icon"); | |||
} | |||
} | |||
@@ -124,7 +124,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
@Override | |||
public String getDisplayString() { | |||
final StringBuffer sb = new StringBuffer(); | |||
final Icon icon = client.getIcon(iconUri); | |||
final Icon icon = client.getIcon(client | |||
.translateVaadinUri(untranslatedIconUri)); | |||
if (icon != null) { | |||
sb.append(icon.getElement().getString()); | |||
} | |||
@@ -164,7 +165,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
* @return | |||
*/ | |||
public String getIconUri() { | |||
return iconUri; | |||
return client.translateVaadinUri(untranslatedIconUri); | |||
} | |||
/** | |||
@@ -190,8 +191,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, | |||
|| (caption != null && !caption.equals(other.caption))) { | |||
return false; | |||
} | |||
if ((iconUri == null && other.iconUri != null) | |||
|| (iconUri != null && !iconUri.equals(other.iconUri))) { | |||
if (!SharedUtil.equals(untranslatedIconUri, | |||
other.untranslatedIconUri)) { | |||
return false; | |||
} | |||
return true; |
@@ -1647,8 +1647,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, | |||
actionMap.put(key + "_c", caption); | |||
if (action.hasAttribute("icon")) { | |||
// TODO need some uri handling ?? | |||
actionMap.put(key + "_i", client.translateVaadinUri(action | |||
.getStringAttribute("icon"))); | |||
actionMap.put(key + "_i", action.getStringAttribute("icon")); | |||
} else { | |||
actionMap.remove(key + "_i"); | |||
} | |||
@@ -1661,7 +1660,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, | |||
} | |||
public String getActionIcon(String actionKey) { | |||
return actionMap.get(actionKey + "_i"); | |||
return client.translateVaadinUri(actionMap.get(actionKey + "_i")); | |||
} | |||
private void updateHeader(String[] strings) { |
@@ -1005,6 +1005,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
activeTheme); | |||
} | |||
String oldThemeBase = getConnection().translateVaadinUri("theme://"); | |||
activeTheme = newTheme; | |||
if (newTheme != null) { | |||
@@ -1013,12 +1015,61 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
activeTheme); | |||
updateVaadinFavicon(newTheme); | |||
} | |||
forceStateChangeRecursively(UIConnector.this); | |||
// UIDL has no stored URL which we can repaint so we do some find and | |||
// replace magic... | |||
String newThemeBase = getConnection().translateVaadinUri("theme://"); | |||
replaceThemeAttribute(oldThemeBase, newThemeBase); | |||
getLayoutManager().forceLayout(); | |||
} | |||
/** | |||
* Finds all attributes where theme:// urls have possibly been used and | |||
* replaces any old theme url with a new one | |||
* | |||
* @param oldPrefix | |||
* The start of the old theme URL | |||
* @param newPrefix | |||
* The start of the new theme URL | |||
*/ | |||
private void replaceThemeAttribute(String oldPrefix, String newPrefix) { | |||
// Images | |||
replaceThemeAttribute("src", oldPrefix, newPrefix); | |||
// Embedded flash | |||
replaceThemeAttribute("value", oldPrefix, newPrefix); | |||
replaceThemeAttribute("movie", oldPrefix, newPrefix); | |||
} | |||
/** | |||
* Finds any attribute of the given type where theme:// urls have possibly | |||
* been used and replaces any old theme url with a new one | |||
* | |||
* @param attributeName | |||
* The name of the attribute, e.g. "src" | |||
* @param oldPrefix | |||
* The start of the old theme URL | |||
* @param newPrefix | |||
* The start of the new theme URL | |||
*/ | |||
private void replaceThemeAttribute(String attributeName, String oldPrefix, | |||
String newPrefix) { | |||
// Find all "attributeName=" which start with "oldPrefix" using e.g. | |||
// [^src='http://oldpath'] | |||
NodeList<Element> elements = querySelectorAll("[" + attributeName | |||
+ "^='" + oldPrefix + "']"); | |||
for (int i = 0; i < elements.getLength(); i++) { | |||
Element element = elements.getItem(i); | |||
element.setAttribute( | |||
attributeName, | |||
element.getAttribute(attributeName).replace(oldPrefix, | |||
newPrefix)); | |||
} | |||
} | |||
/** | |||
* Force a full recursive recheck of every connector's state variables. | |||
* |
@@ -0,0 +1,101 @@ | |||
/* | |||
* Copyright 2000-2013 Vaadin Ltd. | |||
* | |||
* 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 com.vaadin.tests.themes; | |||
import com.vaadin.annotations.Theme; | |||
import com.vaadin.event.Action; | |||
import com.vaadin.event.Action.Handler; | |||
import com.vaadin.server.ThemeResource; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.tests.util.PersonContainer; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.ComboBox; | |||
import com.vaadin.ui.Embedded; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.MenuBar; | |||
import com.vaadin.ui.Table; | |||
import com.vaadin.ui.VerticalLayout; | |||
@Theme("reindeer") | |||
public class LegacyComponentThemeChange extends AbstractTestUIWithLog { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
VerticalLayout vl = new VerticalLayout(); | |||
vl.setCaption("Change theme by clicking a button"); | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
for (final String theme : new String[] { "reindeer", "runo" }) { | |||
Button b = new Button(theme); | |||
b.setId(theme + ""); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
getUI().setTheme(theme); | |||
} | |||
}); | |||
hl.addComponent(b); | |||
} | |||
vl.addComponent(hl); | |||
// Always wants to use icon from Runo, even if we change theme | |||
ThemeResource alwaysTheSameIconImage = new ThemeResource( | |||
"../runo/icons/16/ok.png"); | |||
ThemeResource varyingIcon = new ThemeResource("menubar-theme-icon.png"); | |||
MenuBar bar = new MenuBar(); | |||
bar.addItem("runo", alwaysTheSameIconImage, null); | |||
bar.addItem("seletedtheme", varyingIcon, null); | |||
vl.addComponent(bar); | |||
ComboBox cb = new ComboBox("ComboBox"); | |||
cb.addItem("No icon"); | |||
cb.addItem("Icon"); | |||
cb.setItemIcon("Icon", new ThemeResource("comboboxicon.png")); | |||
cb.setValue("Icon"); | |||
vl.addComponent(cb); | |||
Embedded e = new Embedded("embedded"); | |||
e.setMimeType("application/x-shockwave-flash"); | |||
e.setType(Embedded.TYPE_OBJECT); | |||
e.setSource(new ThemeResource("embedded.src")); | |||
vl.addComponent(e); | |||
Table t = new Table(); | |||
t.addActionHandler(new Handler() { | |||
@Override | |||
public void handleAction(Action action, Object sender, Object target) { | |||
} | |||
@Override | |||
public Action[] getActions(Object target, Object sender) { | |||
return new Action[] { new Action("Theme icon", | |||
new ThemeResource("action-icon.png")) }; | |||
} | |||
}); | |||
PersonContainer pc = PersonContainer.createWithTestData(); | |||
pc.addNestedContainerBean("address"); | |||
t.setContainerDataSource(pc); | |||
vl.addComponent(t); | |||
addComponent(vl); | |||
} | |||
} |
@@ -0,0 +1,163 @@ | |||
/* | |||
* Copyright 2000-2013 Vaadin Ltd. | |||
* | |||
* 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 com.vaadin.tests.themes; | |||
import java.util.List; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.elements.ComboBoxElement; | |||
import com.vaadin.testbench.elements.EmbeddedElement; | |||
import com.vaadin.testbench.elements.MenuBarElement; | |||
import com.vaadin.testbench.elements.TableElement; | |||
import com.vaadin.testbench.parallel.Browser; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class LegacyComponentThemeChangeTest extends MultiBrowserTest { | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
// Seems like stylesheet onload is not fired on PhantomJS | |||
// https://github.com/ariya/phantomjs/issues/12332 | |||
List<DesiredCapabilities> l = getBrowsersExcludingPhantomJS(); | |||
// For some reason, IE times out when trying to open the combobox, | |||
// #18341 | |||
l.remove(Browser.IE11.getDesiredCapabilities()); | |||
return l; | |||
} | |||
@Test | |||
public void legacyComponentThemeResourceChange() { | |||
openTestURL(); | |||
String theme = "reindeer"; | |||
assertMenubarTheme(theme); | |||
assertCombobBoxTheme(theme); | |||
assertTableTheme(theme); | |||
assertEmbeddedTheme(theme); | |||
theme = "runo"; | |||
changeTheme(theme); | |||
assertMenubarTheme(theme); | |||
assertCombobBoxTheme(theme); | |||
assertTableTheme(theme); | |||
assertEmbeddedTheme(theme); | |||
theme = "reindeer"; | |||
changeTheme(theme); | |||
assertMenubarTheme(theme); | |||
assertCombobBoxTheme(theme); | |||
assertTableTheme(theme); | |||
assertEmbeddedTheme(theme); | |||
} | |||
private void assertEmbeddedTheme(String theme) { | |||
if (BrowserUtil.isIE8(getDesiredCapabilities())) { | |||
// IE8 won't initialize the dummy flash properly | |||
return; | |||
} | |||
EmbeddedElement e = $(EmbeddedElement.class).first(); | |||
WebElement movieParam = e.findElement(By | |||
.xpath(".//param[@name='movie']")); | |||
WebElement embed = e.findElement(By.xpath(".//embed")); | |||
assertAttributePrefix(movieParam, "value", theme); | |||
assertAttributePrefix(embed, "src", theme); | |||
assertAttributePrefix(embed, "movie", theme); | |||
} | |||
private void assertTableTheme(String theme) { | |||
TableElement t = $(TableElement.class).first(); | |||
t.getRow(0).contextClick(); | |||
WebElement popup = findElement(By.className("v-contextmenu")); | |||
WebElement actionImage = popup.findElement(By.xpath(".//img")); | |||
assertAttributePrefix(actionImage, "src", theme); | |||
} | |||
private void assertCombobBoxTheme(String theme) { | |||
ComboBoxElement cb = $(ComboBoxElement.class).first(); | |||
WebElement selectedImage = cb.findElement(By.xpath("./img")); | |||
assertAttributePrefix(selectedImage, "src", theme); | |||
cb.openPopup(); | |||
WebElement popup = findElement(By | |||
.className("v-filterselect-suggestpopup")); | |||
WebElement itemImage = popup.findElement(By.xpath(".//img")); | |||
assertAttributePrefix(itemImage, "src", theme); | |||
} | |||
private void assertMenubarTheme(String theme) { | |||
// The runoImage must always come from Runo | |||
WebElement runoImage = $(MenuBarElement.class).first().findElement( | |||
By.xpath(".//span[text()='runo']/img")); | |||
String runoImageSrc = runoImage.getAttribute("src"); | |||
// Something in Selenium normalizes the image so it becomes | |||
// "/themes/runo/icons/16/ok.png" here although it is | |||
// "/themes/<currenttheme>/../runo/icons/16/ok.png" in the browser | |||
Assert.assertEquals(getThemeURL("runo") + "icons/16/ok.png", | |||
runoImageSrc); | |||
// The other image should change with the theme | |||
WebElement themeImage = $(MenuBarElement.class).first().findElement( | |||
By.xpath(".//span[text()='seletedtheme']/img")); | |||
assertAttributePrefix(themeImage, "src", theme); | |||
} | |||
private void assertAttributePrefix(WebElement element, String attribute, | |||
String theme) { | |||
String value = element.getAttribute(attribute); | |||
String expectedPrefix = getThemeURL(theme); | |||
Assert.assertTrue("Attribute " + attribute + "='" + value | |||
+ "' does not start with " + expectedPrefix, | |||
value.startsWith(expectedPrefix)); | |||
} | |||
private String getThemeURL(String theme) { | |||
return getBaseURL() + "/VAADIN/themes/" + theme + "/"; | |||
} | |||
private void changeTheme(String theme) { | |||
$(ButtonElement.class).id(theme).click(); | |||
waitForThemeToChange(theme); | |||
} | |||
private void waitForThemeToChange(final String theme) { | |||
final WebElement rootDiv = findElement(By | |||
.xpath("//div[contains(@class,'v-app')]")); | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
String rootClass = rootDiv.getAttribute("class").trim(); | |||
return rootClass.contains(theme); | |||
} | |||
}, 30); | |||
} | |||
} |