Change-Id: Ib7f6e67c242449e58a10359c596489fea2f679f6tags/7.4.0.beta2
@@ -112,7 +112,7 @@ | |||
<li>Declarative layout support for initializing a component hierarchy from an HTML file.</li> | |||
<li>Uses GWT 2.7 for improved compilation times when using Super Dev Mode.</li> | |||
<li>@Viewport annotation for declaratively defining a mobile viewport definition for a UI.</li> | |||
<li>Captions can be configured to be displayed as HTML.</li> | |||
<li>Component captions, TabSheet/Accordion tab captions and Calendar event captions can be configured to be displayed as HTML.</li> | |||
<li>Selects use converters when presenting itemids.</li> | |||
<li>Improved performance when server response contains no visual changing (e.g. empty polling responses).</li> | |||
<li>Unified JSON library for using the same API in both server-side and client-side code.</li> | |||
@@ -134,6 +134,7 @@ | |||
<p>Raw JSON values passed to AbstractJavaScriptComponent.callFunction and AbstractJavaScriptExtension.callFunction should be changed to use elemental.json types.</p> | |||
</li> | |||
<li>The semantics of empty and required for Field classes has been made more consistent. This mainly affects Checkbox which is now considered to be empty when it is not checked.</li> | |||
<li>The previously inconsistent behavior in HTML vs plain text rendering of Calendar event captions has been made consistent.</li> | |||
<li>Support for Opera 12 has been dropped. Newer versions based on the Blink rendering engine are still supported.</li> | |||
</ul> | |||
<h3 id="knownissues">Known issues</h3> |
@@ -1342,6 +1342,7 @@ public class VCalendar extends Composite implements VHasDropHandler { | |||
private MouseEventListener mouseEventListener; | |||
private boolean forwardNavigationEnabled = true; | |||
private boolean backwardNavigationEnabled = true; | |||
private boolean eventCaptionAsHtml = false; | |||
/** | |||
* Get the listener that listen to mouse events | |||
@@ -1467,4 +1468,33 @@ public class VCalendar extends Composite implements VHasDropHandler { | |||
public void setDropHandler(CalendarDropHandler dropHandler) { | |||
this.dropHandler = dropHandler; | |||
} | |||
/** | |||
* Sets whether the event captions are rendered as HTML. | |||
* <p> | |||
* If set to true, the captions are rendered in the browser as HTML and the | |||
* developer is responsible for ensuring no harmful HTML is used. If set to | |||
* false, the caption is rendered in the browser as plain text. | |||
* <p> | |||
* The default is false, i.e. to render that caption as plain text. | |||
* | |||
* @param captionAsHtml | |||
* true if the captions are rendered as HTML, false if rendered | |||
* as plain text | |||
*/ | |||
public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) { | |||
this.eventCaptionAsHtml = eventCaptionAsHtml; | |||
} | |||
/** | |||
* Checks whether event captions are rendered as HTML | |||
* <p> | |||
* The default is false, i.e. to render that caption as plain text. | |||
* | |||
* @return true if the captions are rendered as HTML, false if rendered as | |||
* plain text | |||
*/ | |||
public boolean isEventCaptionAsHtml() { | |||
return eventCaptionAsHtml; | |||
} | |||
} |
@@ -345,6 +345,8 @@ public class CalendarConnector extends AbstractComponentConnector implements | |||
widget.setEventMoveAllowed(hasEventListener(CalendarEventId.EVENTMOVE)); | |||
widget.setEventResizeAllowed(hasEventListener(CalendarEventId.EVENTRESIZE)); | |||
widget.setEventCaptionAsHtml(state.eventCaptionAsHtml); | |||
List<CalendarState.Day> days = state.days; | |||
List<CalendarState.Event> events = state.events; | |||
@@ -184,14 +184,20 @@ public class DateCellDayEvent extends FocusableHTML implements | |||
*/ | |||
private void updateCaptions(boolean bigMode) { | |||
String innerHtml; | |||
String escapedCaption = Util.escapeHTML(calendarEvent.getCaption()); | |||
String timeAsText = calendarEvent.getTimeAsText(); | |||
String htmlOrText; | |||
if (dateCell.weekgrid.getCalendar().isEventCaptionAsHtml()) { | |||
htmlOrText = calendarEvent.getCaption(); | |||
} else { | |||
htmlOrText = Util.escapeHTML(calendarEvent.getCaption()); | |||
} | |||
if (bigMode) { | |||
innerHtml = "<span>" + timeAsText + "</span><br />" | |||
+ escapedCaption; | |||
innerHtml = "<span>" + timeAsText + "</span><br />" + htmlOrText; | |||
} else { | |||
innerHtml = "<span>" + timeAsText + "<span>:</span></span> " | |||
+ escapedCaption; | |||
+ htmlOrText; | |||
} | |||
caption.setInnerHTML(innerHtml); | |||
eventContent.setInnerHTML(""); |
@@ -20,6 +20,7 @@ import java.util.Date; | |||
import com.google.gwt.event.dom.client.ContextMenuEvent; | |||
import com.google.gwt.event.dom.client.ContextMenuHandler; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.vaadin.client.Util; | |||
import com.vaadin.client.ui.VCalendar; | |||
/** | |||
@@ -75,7 +76,8 @@ public class MonthEventLabel extends HTML implements HasTooltipKey { | |||
* Set the caption of the event label | |||
* | |||
* @param caption | |||
* The caption string, can be HTML | |||
* The caption string, can be HTML if | |||
* {@link VCalendar#isEventCaptionAsHtml()} is true | |||
*/ | |||
public void setCaption(String caption) { | |||
this.caption = caption; | |||
@@ -87,13 +89,20 @@ public class MonthEventLabel extends HTML implements HasTooltipKey { | |||
*/ | |||
private void renderCaption() { | |||
StringBuilder html = new StringBuilder(); | |||
String textOrHtml; | |||
if (calendar.isEventCaptionAsHtml()) { | |||
textOrHtml = caption; | |||
} else { | |||
textOrHtml = Util.escapeHTML(caption); | |||
} | |||
if (caption != null && time != null) { | |||
html.append("<span class=\"" + STYLENAME + "-time\">"); | |||
html.append(calendar.getTimeFormat().format(time)); | |||
html.append("</span> "); | |||
html.append(caption); | |||
html.append(textOrHtml); | |||
} else if (caption != null) { | |||
html.append(caption); | |||
html.append(textOrHtml); | |||
} else if (time != null) { | |||
html.append("<span class=\"" + STYLENAME + "-time\">"); | |||
html.append(calendar.getTimeFormat().format(time)); |
@@ -102,7 +102,11 @@ public class WeeklyLongEvents extends HorizontalPanel implements HasTooltipKey { | |||
eventLabel.addStyleDependentName(extraStyle + "-all-day"); | |||
} | |||
if (!started) { | |||
eventLabel.setText(calendarEvent.getCaption()); | |||
if (calendar.isEventCaptionAsHtml()) { | |||
eventLabel.setHTML(calendarEvent.getCaption()); | |||
} else { | |||
eventLabel.setText(calendarEvent.getCaption()); | |||
} | |||
started = true; | |||
} | |||
} |
@@ -296,6 +296,11 @@ public class Calendar extends AbstractComponent implements | |||
return (CalendarState) super.getState(); | |||
} | |||
@Override | |||
protected CalendarState getState(boolean markAsDirty) { | |||
return (CalendarState) super.getState(markAsDirty); | |||
} | |||
@Override | |||
public void beforeClientResponse(boolean initial) { | |||
super.beforeClientResponse(initial); | |||
@@ -1667,7 +1672,7 @@ public class Calendar extends AbstractComponent implements | |||
* weekly mode | |||
*/ | |||
public boolean isMonthlyMode() { | |||
CalendarState state = (CalendarState) getState(false); | |||
CalendarState state = getState(false); | |||
if (state.days != null) { | |||
return state.days.size() > 7; | |||
} else { | |||
@@ -1895,4 +1900,34 @@ public class Calendar extends AbstractComponent implements | |||
dropHandler.getAcceptCriterion().paint(target); | |||
} | |||
} | |||
/** | |||
* Sets whether the event captions are rendered as HTML. | |||
* <p> | |||
* If set to true, the captions are rendered in the browser as HTML and the | |||
* developer is responsible for ensuring no harmful HTML is used. If set to | |||
* false, the caption is rendered in the browser as plain text. | |||
* <p> | |||
* The default is false, i.e. to render that caption as plain text. | |||
* | |||
* @param captionAsHtml | |||
* true if the captions are rendered as HTML, false if rendered | |||
* as plain text | |||
*/ | |||
public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) { | |||
getState().eventCaptionAsHtml = eventCaptionAsHtml; | |||
} | |||
/** | |||
* Checks whether event captions are rendered as HTML | |||
* <p> | |||
* The default is false, i.e. to render that caption as plain text. | |||
* | |||
* @return true if the captions are rendered as HTML, false if rendered as | |||
* plain text | |||
*/ | |||
public boolean isEventCaptionAsHtml() { | |||
return getState(false).eventCaptionAsHtml; | |||
} | |||
} |
@@ -38,6 +38,7 @@ public class CalendarState extends AbstractComponentState { | |||
public List<CalendarState.Day> days; | |||
public List<CalendarState.Event> events; | |||
public List<CalendarState.Action> actions; | |||
public boolean eventCaptionAsHtml; | |||
public static class Day implements java.io.Serializable { | |||
public String date; |
@@ -0,0 +1,101 @@ | |||
/* | |||
* Copyright 2000-2014 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.components.calendar; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.Property.ValueChangeListener; | |||
import com.vaadin.data.util.MethodProperty; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.Calendar; | |||
import com.vaadin.ui.CheckBox; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.NativeSelect; | |||
import com.vaadin.ui.components.calendar.event.BasicEvent; | |||
import com.vaadin.ui.components.calendar.event.CalendarEvent; | |||
import com.vaadin.ui.components.calendar.event.CalendarEventProvider; | |||
public class CalendarHtmlInEvents extends AbstractTestUIWithLog { | |||
private Calendar calendar = new Calendar(); | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
final NativeSelect ns = new NativeSelect("Period"); | |||
ns.addItems("Day", "Week", "Month"); | |||
ns.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
if ("Day".equals(ns.getValue())) { | |||
calendar.setStartDate(new Date(2014 - 1900, 1 - 1, 1)); | |||
calendar.setEndDate(new Date(2014 - 1900, 1 - 1, 1)); | |||
} else if ("Week".equals(ns.getValue())) { | |||
calendar.setStartDate(new Date(2014 - 1900, 1 - 1, 1)); | |||
calendar.setEndDate(new Date(2014 - 1900, 1 - 1, 7)); | |||
} else if ("Month".equals(ns.getValue())) { | |||
calendar.setStartDate(new Date(2014 - 1900, 1 - 1, 1)); | |||
calendar.setEndDate(new Date(2014 - 1900, 2 - 1, 1)); | |||
} | |||
} | |||
}); | |||
ns.setValue("Month"); | |||
final CheckBox allowHtml = new CheckBox("Allow HTML in event caption", | |||
new MethodProperty<Boolean>(calendar, "eventCaptionAsHtml")); | |||
allowHtml.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
log("HTML in event caption: " + allowHtml.getValue()); | |||
} | |||
}); | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.setDefaultComponentAlignment(Alignment.BOTTOM_LEFT); | |||
hl.addComponents(ns, allowHtml); | |||
hl.setSpacing(true); | |||
hl.setMargin(true); | |||
calendar.setEventProvider(new CalendarEventProvider() { | |||
@Override | |||
public List<CalendarEvent> getEvents(Date startDate, Date endDate) { | |||
Date d = startDate; | |||
ArrayList<CalendarEvent> events = new ArrayList<CalendarEvent>(); | |||
while (d.before(endDate)) { | |||
BasicEvent ce = new BasicEvent(); | |||
ce.setAllDay(false); | |||
ce.setCaption("<b>Hello</b> <u>world</u>!"); | |||
ce.setDescription("Nothing really important"); | |||
Date start = new Date(d.getTime()); | |||
start.setHours(d.getDay()); | |||
Date end = new Date(d.getTime()); | |||
end.setHours(d.getDay() + 3); | |||
ce.setStart(start); | |||
ce.setEnd(end); | |||
events.add(ce); | |||
d.setTime(d.getTime() + 1000 * 60 * 60 * 24); | |||
} | |||
return events; | |||
} | |||
}); | |||
addComponent(hl); | |||
addComponent(calendar); | |||
} | |||
} |
@@ -0,0 +1,103 @@ | |||
/* | |||
* Copyright 2000-2014 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.components.calendar; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.testbench.elements.CalendarElement; | |||
import com.vaadin.testbench.elements.CheckBoxElement; | |||
import com.vaadin.testbench.elements.NativeSelectElement; | |||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||
public class CalendarHtmlInEventsTest extends SingleBrowserTest { | |||
private NativeSelectElement periodSelect; | |||
private CheckBoxElement htmlAllowed; | |||
private CalendarElement calendar; | |||
@Override | |||
public void setup() throws Exception { | |||
super.setup(); | |||
openTestURL(); | |||
periodSelect = $(NativeSelectElement.class).first(); | |||
htmlAllowed = $(CheckBoxElement.class).first(); | |||
calendar = $(CalendarElement.class).first(); | |||
} | |||
@Test | |||
public void monthViewEventCaptions() { | |||
Assert.assertEquals(getMonthEvent(0).getText(), | |||
"12:00 AM <b>Hello</b> <u>world</u>!"); | |||
// Switch to HTML mode | |||
click(htmlAllowed); | |||
Assert.assertEquals("1. HTML in event caption: true", getLogRow(0)); | |||
Assert.assertEquals(getMonthEvent(0).getText(), "12:00 AM Hello world!"); | |||
} | |||
@Test | |||
public void weekViewEventCaptions() { | |||
periodSelect.selectByText("Week"); | |||
Assert.assertEquals("4:00 AM\n<b>Hello</b> <u>world</u>!", | |||
getWeekEvent(1).getText()); | |||
// Switch to HTML mode | |||
click(htmlAllowed); | |||
Assert.assertEquals("1. HTML in event caption: true", getLogRow(0)); | |||
Assert.assertEquals("4:00 AM\nHello world!", getWeekEvent(1).getText()); | |||
} | |||
@Test | |||
public void dayViewEventCaptions() { | |||
periodSelect.selectByText("Day"); | |||
Assert.assertEquals("3:00 AM\n<b>Hello</b> <u>world</u>!", | |||
getWeekEvent(0).getText()); | |||
// Switch to HTML mode | |||
click(htmlAllowed); | |||
Assert.assertEquals("1. HTML in event caption: true", getLogRow(0)); | |||
Assert.assertEquals("3:00 AM\nHello world!", getWeekEvent(0).getText()); | |||
} | |||
private WebElement getMonthEvent(int dayInCalendar) { | |||
return getMonthDay(dayInCalendar).findElement( | |||
By.className("v-calendar-event")); | |||
} | |||
private WebElement getWeekEvent(int dayInCalendar) { | |||
return getWeekDay(dayInCalendar).findElement( | |||
By.className("v-calendar-event")); | |||
} | |||
private void click(CheckBoxElement htmlAllowed2) { | |||
htmlAllowed2.findElement(By.xpath("input")).click(); | |||
} | |||
private WebElement getMonthDay(int i) { | |||
return calendar.findElements(By.className("v-calendar-month-day")).get( | |||
i); | |||
} | |||
private WebElement getWeekDay(int i) { | |||
return calendar.findElements(By.className("v-calendar-day-times")).get( | |||
i); | |||
} | |||
} |