implemented automatic notifications scroll back in time

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/notifications@94114 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Massimiliano Assante 2014-04-02 16:02:48 +00:00
parent 2dca13e08a
commit b4506624c4
16 changed files with 206 additions and 128 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/notifications-1.1.0-SNAPSHOT/WEB-INF/classes" path="src/main/java">
<classpathentry kind="src" output="target/notifications-1.2.0-SNAPSHOT/WEB-INF/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
@ -30,5 +30,5 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/notifications-1.1.0-SNAPSHOT/WEB-INF/classes"/>
<classpathentry kind="output" path="target/notifications-1.2.0-SNAPSHOT/WEB-INF/classes"/>
</classpath>

View File

@ -1,5 +1,5 @@
eclipse.preferences.version=1
jarsExcludedFromWebInfLib=
lastWarOutDir=/Users/massi/Documents/workspace/notifications/target/notifications-1.1.0-SNAPSHOT
lastWarOutDir=/Users/massi/Documents/workspace/notifications/target/notifications-1.2.0-SNAPSHOT
warSrcDir=src/main/webapp
warSrcDirIsOutput=false

View File

@ -1,13 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="${module}">
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<dependent-module archiveName="switch-button-widget-1.0.0-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/switch-button-widget/switch-button-widget">
<dependency-type>uses</dependency-type>
</dependent-module>
<property name="java-output-path" value="/${module}/target/www/WEB-INF/classes"/>
<property name="context-root" value="notifications"/>
</wb-module>

23
pom.xml
View File

@ -13,14 +13,13 @@
<groupId>org.gcube.portlets.user</groupId>
<artifactId>notifications</artifactId>
<packaging>war</packaging>
<version>1.1.0-SNAPSHOT</version>
<version>1.2.0-SNAPSHOT</version>
<name>gCube Notifications Portlet</name>
<properties>
<!-- Convenience property to set the GWT version -->
<gwtVersion>2.5.1</gwtVersion>
<distroDirectory>distro</distroDirectory>
<!-- GWT needs at least java 1.6 -->
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
@ -52,7 +51,7 @@
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<scope>provided</scope>
</dependency>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
@ -72,6 +71,12 @@
<artifactId>switch-button-widget</artifactId>
<version>[1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
</dependency>
<dependency>
<groupId>org.gcube.portal</groupId>
<artifactId>social-networking-library</artifactId>
<version>[1.3.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube.portal</groupId>
<artifactId>custom-portal-handler</artifactId>
@ -101,18 +106,6 @@
<artifactId>portlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube.portal</groupId>
<artifactId>social-networking-library</artifactId>
<version>[1.3.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -5,7 +5,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import org.gcube.portal.databook.shared.Notification;
import org.gcube.portal.databook.shared.NotificationChannelType;
@ -24,6 +23,8 @@ public interface NotificationsService extends RemoteService {
UserInfo getUserInfo();
HashMap<Date, ArrayList<Notification>> getUserNotifications();
HashMap<Date, ArrayList<Notification>> getUserNotificationsByRange(int from, int quantity);
boolean setAllUserNotificationsRead();
LinkedHashMap<String, ArrayList<NotificationPreference>> getUserNotificationPreferences();

View File

@ -30,6 +30,7 @@ public interface NotificationsServiceAsync {
Map<NotificationType, NotificationChannelType[]> enabledChannels,
AsyncCallback<Boolean> callback);
void getUserNotificationsByRange(int from, int quantity,
AsyncCallback<HashMap<Date, ArrayList<Notification>>> callback);
}

View File

@ -5,17 +5,15 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import org.gcube.portal.databook.shared.Notification;
import org.gcube.portal.databook.shared.NotificationChannelType;
import org.gcube.portal.databook.shared.NotificationType;
import org.gcube.portal.databook.shared.UserInfo;
import org.gcube.portlets.user.notifications.client.NotificationsService;
import org.gcube.portlets.user.notifications.client.NotificationsServiceAsync;
import org.gcube.portlets.user.notifications.client.view.templates.DayWrapper;
import org.gcube.portlets.user.notifications.client.view.templates.ShowMoreNotifications;
import org.gcube.portlets.user.notifications.client.view.templates.SingleNotificationView;
import org.gcube.portlets.user.notifications.shared.NotificationConstants;
import org.gcube.portlets.user.notifications.shared.NotificationPreference;
import com.google.gwt.core.client.GWT;
@ -25,6 +23,8 @@ import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ScrollEvent;
import com.google.gwt.user.client.Window.ScrollHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
@ -33,8 +33,8 @@ import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* @author Massimiliano Assante ISTI-CNR
@ -51,10 +51,13 @@ public class NotificationsPanel extends Composite {
private static final String warning = GWT.getModuleBaseURL() + "../images/warning_blue.png";
private UserInfo myUserInfo;
private Image loadingImage;
private ShowMoreNotifications showMoreWidget;
//needed to know the next range start
private int fromStartingPoint = 0;
private VerticalPanel container = new VerticalPanel();
private HorizontalPanel settingsPanel = new HorizontalPanel();
private VerticalPanel showMoreNotificationsPanel = new VerticalPanel();
private VerticalPanel loadingPanel = new VerticalPanel();
private VerticalPanel mainPanel;
@ -67,6 +70,7 @@ public class NotificationsPanel extends Composite {
mainPanel.setWidth("100%");
container.setWidth("100%");
settingsPanel.setWidth("100%");
showMoreNotificationsPanel.setWidth("100%");
settingsPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
settingsPanel.add(notificationSettings);
container.add(settingsPanel);
@ -110,6 +114,17 @@ public class NotificationsPanel extends Composite {
});
}
});
//this is for the automatic scroll of feeds
Window.addWindowScrollHandler(new ScrollHandler() {
@Override
public void onWindowScroll(ScrollEvent event) {
boolean isInView = isScrolledIntoView(showMoreWidget);
if (isInView) {
doShowMoreNotifications();
}
}
});
}
@ -154,6 +169,15 @@ public class NotificationsPanel extends Composite {
}
if (notCounter > 5 && notCounter < 10)
mainPanel.add(new Image(spacer));
//if you are showing more than NotificationConstants.NOTIFICATION_NUMBER
if (notCounter >= NotificationConstants.NOTIFICATION_NUMBER) {
GWT.log("Show MORE ");
showMoreNotificationsPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
showMoreWidget = new ShowMoreNotifications();
showMoreNotificationsPanel.add(showMoreWidget);
mainPanel.add(showMoreNotificationsPanel);
}
}
}
else
@ -162,6 +186,70 @@ public class NotificationsPanel extends Composite {
});
}
/**
* called when a user scroll down the page to the bottom
*/
private void doShowMoreNotifications() {
showMoreNotificationsPanel.remove(0);
loadingImage.getElement().getStyle().setMargin(10, Unit.PX);
showMoreNotificationsPanel.add(loadingImage);
int from = (fromStartingPoint == 0) ? NotificationConstants.NOTIFICATION_NUMBER+1 : fromStartingPoint;
fromStartingPoint = from;
final int quantity = 20;
GWT.log("StartingPoint = " + from);
notificationService.getUserNotificationsByRange(from, quantity, new AsyncCallback<HashMap<Date,ArrayList<Notification>>>() {
@Override
public void onSuccess(HashMap<Date, ArrayList<Notification>> notificationsPerDay) {
mainPanel.remove(showMoreNotificationsPanel);
if (notificationsPerDay != null) {
ArrayList<Date> sortedKeys=new ArrayList<Date>(notificationsPerDay.keySet());
Collections.sort(sortedKeys, Collections.reverseOrder());
int notCounter = 0;
for (Date day : sortedKeys) {
mainPanel.add(new DayWrapper(day));
for (Notification notif : notificationsPerDay.get(day)) {
mainPanel.add(new SingleNotificationView(notif));
notCounter++;
}
}
fromStartingPoint += notCounter;
GWT.log("StartingPoint incremented = " + fromStartingPoint);
if (notCounter >= quantity) { //there could be more notifications
GWT.log("Show MORE ");
showMoreNotificationsPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
showMoreWidget = new ShowMoreNotifications();
showMoreNotificationsPanel.add(showMoreWidget);
mainPanel.add(showMoreNotificationsPanel);
}
}
}
@Override
public void onFailure(Throwable caught) {
showMoreNotificationsPanel.clear();
mainPanel.add(new HTML("<div class=\"no-notification-message\">" +
"Ops! There were problems while retrieving your Notifications!. <br> " +
"Please try again in a short while.</div>"));
}
});
}
/**
* @param widget the widget to check
* @returnn true if the widget is in the visible part of the page
*/
private boolean isScrolledIntoView(Widget widget) {
if (widget != null) {
int docViewTop = Window.getScrollTop();
int docViewBottom = docViewTop + Window.getClientHeight();
int elemTop = widget.getAbsoluteTop();
int elemBottom = elemTop + widget.getOffsetHeight();
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
return false;
}
private void showLoader() {
mainPanel.clear();
mainPanel.setWidth("100%");

View File

@ -0,0 +1,42 @@
package org.gcube.portlets.user.notifications.client.view.templates;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Widget;
public class ShowMoreNotifications extends Composite {
private static ShowMoreNotificationsUiBinder uiBinder = GWT
.create(ShowMoreNotificationsUiBinder.class);
interface ShowMoreNotificationsUiBinder extends UiBinder<Widget, ShowMoreNotifications> {
}
@UiField HTML caption;
@UiField HTMLPanel panel;
public ShowMoreNotifications() {
initWidget(uiBinder.createAndBindUi(this));
panel.getElement().getStyle().setMarginTop(10, Unit.PX);
caption.addStyleName("new-notifications-show");
caption.getElement().getStyle().setBackgroundColor("transparent");
caption.getElement().getStyle().setFontSize(14, Unit.PX);
caption.setHTML("Show more notifications");
//hiding as not needed anymore (the user click)
panel.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
@UiHandler("caption")
void onClick(ClickEvent e) {
}
}

View File

@ -0,0 +1,7 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<g:HTMLPanel ui:field="panel" styleName="new-feeds-container">
<g:HTML ui:field="caption" styleName="new-feeds-available" />
</g:HTMLPanel>
</ui:UiBinder>

View File

@ -1,5 +1,6 @@
package org.gcube.portlets.user.notifications.client.view.templates;
import org.apache.cassandra.cli.CliParser.attr_name_return;
import org.gcube.portal.databook.client.GCubeSocialNetworking;
import org.gcube.portal.databook.client.util.Encoder;
import org.gcube.portal.databook.shared.Notification;
@ -60,6 +61,8 @@ public class SingleNotificationView extends Composite {
actualHTML = actualHTML.replace("link.", "link");
actualHTML += " you shared.";
}
//shorten the notification text if greather than 200 chars
actualHTML = actualHTML.length() > 200 ? actualHTML.substring(0, 200) + " ..." : actualHTML;
notificationText.setHTML(
"<a class=\"link\" href=\""+GCubeSocialNetworking.USER_PROFILE_LINK+"?"+
@ -67,6 +70,7 @@ public class SingleNotificationView extends Composite {
Encoder.encode(toShow.getSenderid())+"\">"+
toShow.getSenderFullName()+"</a> " + actualHTML);
timeArea.setHTML(DateTimeFormat.getFormat("h:mm a").format(toShow.getTime()));
notificationImage.setResource(getImageType(toShow.getType()));

View File

@ -26,6 +26,7 @@ import org.gcube.portal.databook.shared.NotificationChannelType;
import org.gcube.portal.databook.shared.NotificationType;
import org.gcube.portal.databook.shared.UserInfo;
import org.gcube.portlets.user.notifications.client.NotificationsService;
import org.gcube.portlets.user.notifications.shared.NotificationConstants;
import org.gcube.portlets.user.notifications.shared.NotificationPreference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -74,7 +75,7 @@ public class NotificationsServiceImpl extends RemoteServiceServlet implements No
String user = (String) this.getThreadLocalRequest().getSession().getAttribute(ScopeHelper.USERNAME_ATTRIBUTE);
if (user == null) {
user = "test.user";
// user = "massimiliano.assante";
user = "massimiliano.assante";
_log.warn("USER IS NULL setting "+user+" and Running OUTSIDE PORTAL");
withinPortal = false;
@ -115,12 +116,12 @@ public class NotificationsServiceImpl extends RemoteServiceServlet implements No
}
/**
* returns the notifications separated per days
* returns the notifications separated per day
*/
public HashMap<Date, ArrayList<Notification>> getUserNotifications() {
HashMap<Date, ArrayList<Notification>> toReturn = new HashMap<Date, ArrayList<Notification>>();
try {
for (Notification notification : store.getAllNotificationByUser(getASLSession().getUsername(), 70)) {
for (Notification notification : store.getAllNotificationByUser(getASLSession().getUsername(), NotificationConstants.NOTIFICATION_NUMBER)) {
Date dateWithoutTime = removeTimePart(notification.getTime());
if (! toReturn.containsKey(dateWithoutTime)) {
ArrayList<Notification> nots = new ArrayList<Notification>();
@ -137,6 +138,31 @@ public class NotificationsServiceImpl extends RemoteServiceServlet implements No
}
return toReturn;
}
/**
* returns the notifications in the range separated per day
*/
public HashMap<Date, ArrayList<Notification>> getUserNotificationsByRange(int from, int quantity) {
HashMap<Date, ArrayList<Notification>> toReturn = new HashMap<Date, ArrayList<Notification>>();
try {
for (Notification notification : store.getRangeNotificationsByUser(getASLSession().getUsername(), from, quantity)) {
Date dateWithoutTime = removeTimePart(notification.getTime());
if (! toReturn.containsKey(dateWithoutTime)) {
ArrayList<Notification> nots = new ArrayList<Notification>();
nots.add(notification);
toReturn.put(dateWithoutTime, nots);
} else {
toReturn.get(dateWithoutTime).add(notification);
}
}
} catch (Exception e) {
_log.error("While trying to get User notifications");
e.printStackTrace();
}
return toReturn;
}
/**
* we want notification split per day
* @param date
@ -176,10 +202,10 @@ public class NotificationsServiceImpl extends RemoteServiceServlet implements No
Properties descriptions = getDescriptionsByType();
TreeMap<String, ArrayList<NotificationPreference>> treeMap = new TreeMap<String, ArrayList<NotificationPreference>>();
try {
Map<NotificationType, NotificationChannelType[]> storePreferences = store.getUserNotificationPreferences(userid);
for (NotificationType type : storePreferences.keySet()) {
String category = categories.getProperty(type.toString());
String typeLabel = labels.getProperty(type.toString());

View File

@ -0,0 +1,5 @@
package org.gcube.portlets.user.notifications.shared;
public class NotificationConstants {
public final static int NOTIFICATION_NUMBER = 70;
}

View File

@ -2,10 +2,6 @@
<module rename-to='notifications'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User' />
<!-- We need the JUnit module in the main module, -->
<!-- otherwise eclipse complains (Google plugin bug?) -->
<inherits name='com.google.gwt.junit.JUnit' />
<!-- To Comment out -->
<!-- <set-property name="user.agent" value="gecko1_8" /> -->

View File

@ -51,6 +51,13 @@ a.link:hover {
text-decoration: underline;
}
.new-notifications-show {
opacity: 1;
background: #D6E2FC;
height: 18px;
}
.day-wrapper {
width: 100%;
border-bottom-color: #DADADA;

View File

@ -20,20 +20,6 @@
<url-pattern>/org.gcube.portlets.user.notifications.NotificationsJUnit/notifications/notificationsServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>jUnitHostImpl</servlet-name>
<servlet-class>com.google.gwt.junit.server.JUnitHostImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>jUnitHostImpl</servlet-name>
<url-pattern>/org.gcube.portlets.user.notifications.NotificationsJUnit/junithost/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jUnitHostImpl</servlet-name>
<url-pattern>/notifications/junithost/*</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>Notifications.html</welcome-file>

View File

@ -1,74 +0,0 @@
package org.gcube.portlets.user.notifications.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
/**
* GWT JUnit <b>integration</b> tests must extend GWTTestCase.
* Using <code>"GwtTest*"</code> naming pattern exclude them from running with
* surefire during the test phase.
*
* If you run the tests using the Maven command line, you will have to
* navigate with your browser to a specific url given by Maven.
* See http://mojo.codehaus.org/gwt-maven-plugin/user-guide/testing.html
* for details.
*/
public class GwtTestNotifications extends GWTTestCase {
/**
* Must refer to a valid module that sources this class.
*/
public String getModuleName() {
return "org.gcube.portlets.user.notifications.NotificationsJUnit";
}
/**
* Tests the FieldVerifier.
*/
public void testFieldVerifier() {
// assertFalse(FieldVerifier.isValidName(null));
// assertFalse(FieldVerifier.isValidName(""));
// assertFalse(FieldVerifier.isValidName("a"));
// assertFalse(FieldVerifier.isValidName("ab"));
// assertFalse(FieldVerifier.isValidName("abc"));
// assertTrue(FieldVerifier.isValidName("abcd"));
}
/**
* This test will send a request to the server using the greetServer method in
* NotificationsService and verify the response.
*/
public void testGreetingService() {
// // Create the service that we will test.
// NotificationsServiceAsync greetingService = GWT.create(NotificationsService.class);
// ServiceDefTarget target = (ServiceDefTarget) greetingService;
// target.setServiceEntryPoint(GWT.getModuleBaseURL() + "Notifications/greet");
// Since RPC calls are asynchronous, we will need to wait for a response
// after this test method returns. This line tells the test runner to wait
// up to 10 seconds before timing out.
// delayTestFinish(10000);
// // Send a request to the server.
// greetingService.greetServer("GWT User", new AsyncCallback<String>() {
// public void onFailure(Throwable caught) {
// // The request resulted in an unexpected error.
// fail("Request failure: " + caught.getMessage());
// }
//
// public void onSuccess(String result) {
// // Verify that the response is correct.
// assertTrue(result.startsWith("Hello, GWT User!"));
//
// // Now that we have received a response, we need to tell the test runner
// // that the test is complete. You must call finishTest() after an
// // asynchronous test finishes successfully, or the test will time out.
// finishTest();
// }
// });
}
}