diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..0b062bc
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..42b10c5
--- /dev/null
+++ b/.project
@@ -0,0 +1,49 @@
+
+
+ message-conversations
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.wst.common.project.facet.core.builder
+
+
+
+
+ org.eclipse.wst.validation.validationbuilder
+
+
+
+
+ com.google.gdt.eclipse.core.webAppProjectValidator
+
+
+
+
+ com.google.gwt.eclipse.core.gwtProjectValidator
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jem.workbench.JavaEMFNature
+ org.eclipse.wst.common.modulecore.ModuleCoreNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.wst.common.project.facet.core.nature
+ org.eclipse.wst.jsdt.core.jsNature
+ com.liferay.ide.core.liferayNature
+ com.google.gwt.eclipse.core.gwtNature
+
+
diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope
new file mode 100644
index 0000000..f179e11
--- /dev/null
+++ b/.settings/.jsdtscope
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.settings/com.google.gdt.eclipse.core.prefs b/.settings/com.google.gdt.eclipse.core.prefs
new file mode 100644
index 0000000..d890b4f
--- /dev/null
+++ b/.settings/com.google.gdt.eclipse.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+lastWarOutDir=/Users/massi/Documents/workspace/message-conversations/target/message-conversations-0.0.1-SNAPSHOT
+warSrcDir=src/main/webapp
+warSrcDirIsOutput=false
diff --git a/.settings/com.google.gwt.eclipse.core.prefs b/.settings/com.google.gwt.eclipse.core.prefs
new file mode 100644
index 0000000..fb32c70
--- /dev/null
+++ b/.settings/com.google.gwt.eclipse.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+entryPointModules=
+filesCopiedToWebInfLib=
diff --git a/.settings/com.gwtplugins.gdt.eclipse.core.prefs b/.settings/com.gwtplugins.gdt.eclipse.core.prefs
new file mode 100644
index 0000000..9a193e3
--- /dev/null
+++ b/.settings/com.gwtplugins.gdt.eclipse.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+warSrcDir=src/main/webapp
+warSrcDirIsOutput=false
diff --git a/.settings/com.gwtplugins.gwt.eclipse.core.prefs b/.settings/com.gwtplugins.gwt.eclipse.core.prefs
new file mode 100644
index 0000000..fb32c70
--- /dev/null
+++ b/.settings/com.gwtplugins.gwt.eclipse.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+entryPointModules=
+filesCopiedToWebInfLib=
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..0bf7f20
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//target/generated-sources/gwt=UTF-8
+encoding/=UTF-8
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..6e80039
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/.settings/org.eclipse.jst.jsp.core.prefs b/.settings/org.eclipse.jst.jsp.core.prefs
new file mode 100644
index 0000000..3a5c98d
--- /dev/null
+++ b/.settings/org.eclipse.jst.jsp.core.prefs
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+validateFragments=false
+validation.use-project-settings=true
diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component
new file mode 100644
index 0000000..f48cf24
--- /dev/null
+++ b/.settings/org.eclipse.wst.common.component
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+ uses
+
+
+
+
+
diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml
new file mode 100644
index 0000000..40d1d27
--- /dev/null
+++ b/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container
new file mode 100644
index 0000000..3bd5d0a
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container
@@ -0,0 +1 @@
+org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name
new file mode 100644
index 0000000..05bd71b
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name
@@ -0,0 +1 @@
+Window
\ No newline at end of file
diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs
new file mode 100644
index 0000000..04cad8c
--- /dev/null
+++ b/.settings/org.eclipse.wst.validation.prefs
@@ -0,0 +1,2 @@
+disabled=06target
+eclipse.preferences.version=1
diff --git a/.tern-project b/.tern-project
new file mode 100644
index 0000000..d7fbf24
--- /dev/null
+++ b/.tern-project
@@ -0,0 +1,23 @@
+{
+ "plugins": {
+ "guess-types": {
+
+ },
+ "outline": {
+
+ },
+ "liferay": {
+
+ },
+ "yui3": {
+
+ },
+ "aui2.0.x": {
+
+ }
+ },
+ "libs": [
+ "ecma5",
+ "browser"
+ ]
+}
\ No newline at end of file
diff --git a/distro/LICENSE b/distro/LICENSE
new file mode 100644
index 0000000..2d9616a
--- /dev/null
+++ b/distro/LICENSE
@@ -0,0 +1 @@
+${gcube.license}
\ No newline at end of file
diff --git a/distro/README b/distro/README
new file mode 100644
index 0000000..619958c
--- /dev/null
+++ b/distro/README
@@ -0,0 +1,61 @@
+The gCube System - ${name}
+--------------------------------------------------
+
+${description}
+
+
+${gcube.description}
+
+${gcube.funding}
+
+
+Version
+--------------------------------------------------
+
+${version} (${buildDate})
+
+Please see the file named "changelog.xml" in this directory for the release notes.
+
+
+Authors
+--------------------------------------------------
+
+* Massimiliano Assante (massimiliano.assante-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy).
+
+Maintainers
+-----------
+
+* Francesco Mangiacrapa (francesco.mangiacrapa-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy).
+* Massimiliano Assante (massimiliano.assante-AT-isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy).
+
+Download information
+--------------------------------------------------
+
+Source code is available from SVN:
+ ${scm.url}
+
+Binaries can be downloaded from the gCube website:
+ ${gcube.website}
+
+
+Installation
+--------------------------------------------------
+
+
+Documentation
+--------------------------------------------------
+
+Documentation is available on-line in the gCube Wiki:
+ ${gcube.wikiRoot}/Workspace
+
+Support
+--------------------------------------------------
+
+Bugs and support requests can be reported in the gCube issue tracking tool:
+ ${gcube.issueTracking}
+
+
+Licensing
+--------------------------------------------------
+
+This software is licensed under the terms you may find in the file named "LICENSE" in this directory.
\ No newline at end of file
diff --git a/distro/changelog.xml b/distro/changelog.xml
new file mode 100644
index 0000000..dc49813
--- /dev/null
+++ b/distro/changelog.xml
@@ -0,0 +1,50 @@
+
+
+ Improved look and feel
+ Responsive, works on mobile
+
+
+ Removed ASL Session
+
+
+ [Feature #879] Message Improvements
+
+
+ Upgraded to GWT 2.7
+ [Feature #129] Porting to HL 2.0
+
+
+ Fixed bug on "reply all"
+ Changed icons
+
+
+ Updated pom to support new gcube core (gcube release 3.2)
+
+
+
+ #Ticket 2223. This project was enhancements to gwt 2.5.1
+
+ GCF dependency was removed
+
+
+ Updated show attachments: this functionality now calls the
+ servlet contained in ws-tree
+
+ Error management for attachments: it was added redirect
+ functionality in download servlet
+
+ Fixed Ticket #1348
+
+
+ First Release
+
+
diff --git a/distro/descriptor.xml b/distro/descriptor.xml
new file mode 100644
index 0000000..c597cd2
--- /dev/null
+++ b/distro/descriptor.xml
@@ -0,0 +1,32 @@
+
+ servicearchive
+
+ tar.gz
+
+ /
+
+
+ ${distroDirectory}
+ /
+ true
+
+ README
+ LICENSE
+ changelog.xml
+ profile.xml
+
+ 755
+ true
+
+
+
+
+
+ /${artifactId}
+
+
+
+
diff --git a/distro/profile.xml b/distro/profile.xml
new file mode 100644
index 0000000..51c3b6b
--- /dev/null
+++ b/distro/profile.xml
@@ -0,0 +1,25 @@
+
+
+
+ Service
+
+ ${description}
+ PortletUser
+ ${artifactId}
+ ${version}
+
+
+ ${artifactId}
+ ${version}
+
+ ${groupId}
+ ${artifactId}
+ ${version}
+
+
+ target/${build.finalName}.war
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..755cd00
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,308 @@
+
+
+
+ 4.0.0
+
+ maven-parent
+ org.gcube.tools
+ 1.0.0
+
+
+ org.gcube.portets.user
+ messages
+ war
+ 2.0.0-SNAPSHOT
+
+ gCube Messages Portlet for exchanging messages with other users.
+
+
+ scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations
+ scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations
+ http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/message-conversations
+
+
+
+
+ 2.8.1
+ distro
+
+ 1.8
+ 1.8
+ 2.0-rc6
+ UTF-8
+
+
+
+
+
+ org.gcube.distribution
+ maven-portal-bom
+ LATEST
+ pom
+ import
+
+
+ com.google.gwt
+ gwt
+ ${gwt.version}
+ pom
+ import
+
+
+
+
+
+
+
+
+ org.gcube.portlets.widgets
+ workspace-explorer
+ [1.8.0-SNAPSHOT,2.0.0-SNAPSHOT)
+
+
+ org.gcube.applicationsupportlayer
+ aslsocial
+ provided
+
+
+ org.gcube.common.portal
+ portal-manager
+ provided
+
+
+ org.gcube.portal
+ client-context-library
+ [1.0.0-SNAPSHOT,)
+ compile
+
+
+ javax.portlet
+ portlet-api
+ provided
+
+
+ com.google.gwt
+ gwt-servlet
+ ${gwt.version}
+ provided
+
+
+ com.google.gwt
+ gwt-user
+ ${gwt.version}
+ provided
+
+
+ com.google.gwt
+ gwt-dev
+ ${gwt.version}
+ provided
+
+
+
+ org.gcube.core
+ common-scope-maps
+ compile
+
+
+
+ com.github.gwtmaterialdesign
+ gwt-material
+ ${gwt-material.version}
+
+
+ com.github.gwtmaterialdesign
+ gwt-material-themes
+ ${gwt-material.version}
+
+
+ com.github.gwtmaterialdesign
+ gwt-material-addins
+ ${gwt-material.version}
+
+
+ org.gcube.portal
+ notifications-common-library
+ provided
+
+
+
+ org.gcube.common
+ home-library-jcr
+ provided
+
+
+ org.gcube.common
+ home-library-model
+ provided
+
+
+ org.gcube.common
+ home-library
+ provided
+
+
+ asm-all
+ asm
+
+
+
+
+ xml-apis
+ xml-apis
+ 1.4.01
+
+
+ javax.servlet
+ javax.servlet-api
+
+ provided
+
+
+ org.gcube.dvos
+ usermanagement-core
+ provided
+
+
+ org.gcube.common
+ authorization-client
+ provided
+
+
+ org.gcube.common
+ common-authorization
+ provided
+
+
+ com.liferay.portal
+ portal-service
+ provided
+
+
+ com.liferay.portal
+ util-java
+ provided
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+
+
+ org.codehaus.mojo
+ gwt-maven-plugin
+ 2.8.1
+
+
+
+ compile
+ generateAsync
+
+
+
+
+
+ MessageConversations.html
+
+ org.gcube.portets.user.message_conversations.MessageConversations
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.0.0-M1
+
+ -Xdoclint:none
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.1.1
+
+
+ compile
+
+ exploded
+
+
+
+
+
+ ${webappDirectory}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3.2
+
+
+ ${maven.compiler.target}
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.2
+
+
+ ${distroDirectory}/descriptor.xml
+
+
+
+
+ servicearchive
+ install
+
+ single
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.5
+
+
+ copy-profile
+ install
+
+ copy-resources
+
+
+ target
+
+
+ ${distroDirectory}
+ true
+
+ profile.xml
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java
new file mode 100644
index 0000000..c7af180
--- /dev/null
+++ b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageConversations.java
@@ -0,0 +1,39 @@
+package org.gcube.portets.user.message_conversations.client;
+
+import org.gcube.portets.user.message_conversations.client.ui.ApplicationView;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.ui.RootPanel;
+
+/**
+ * Entry point classes define onModuleLoad().
+ */
+public class MessageConversations implements EntryPoint {
+ public static final String DIV_CONTAINER_ID = "create-users-container";
+ /**
+ * Create a remote service proxy to talk to the server-side Greeting service.
+ */
+
+ private final MessageServiceAsync convService = GWT.create(MessageService.class);
+ private ApplicationView ap;
+
+ /**
+ * This is the entry point method.
+ */
+ public void onModuleLoad() {
+ ((ServiceDefTarget) convService).setServiceEntryPoint("/delegate/message-conversations");
+ ap = new ApplicationView();
+ RootPanel.get(DIV_CONTAINER_ID).add(ap);
+ ap.readUserMessages(false);
+
+ }
+
+
+
+
+
+
+
+}
diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java
new file mode 100644
index 0000000..6384697
--- /dev/null
+++ b/src/main/java/org/gcube/portets/user/message_conversations/client/MessageService.java
@@ -0,0 +1,25 @@
+package org.gcube.portets.user.message_conversations.client;
+
+import java.util.ArrayList;
+
+import org.gcube.portets.user.message_conversations.shared.ConvMessage;
+import org.gcube.portets.user.message_conversations.shared.CurrUserAndPortalUsersWrapper;
+import org.gcube.portets.user.message_conversations.shared.WSUser;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+
+/**
+ * The client side stub for the RPC service.
+ */
+public interface MessageService extends RemoteService {
+
+ ArrayList getMessages(boolean sent);
+ ConvMessage getMessageById(String messageId, boolean sent);
+ CurrUserAndPortalUsersWrapper getWorkspaceUsers();
+ ArrayList searchUsers(String keyword);
+ boolean sendToById(ArrayList recipientIds, ArrayList listAttachmentsId, String subject, String body);
+ boolean deleteMessageById(String messageId, boolean sent);
+ String getAttachmentDownloadURL(String itemId);
+ boolean saveAttachmentToWorkspaceFolder(String itemId, String destinationFolderId);
+ boolean markMessageUnread(String messageId);
+}
diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java b/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java
new file mode 100644
index 0000000..040a4e1
--- /dev/null
+++ b/src/main/java/org/gcube/portets/user/message_conversations/client/Utils.java
@@ -0,0 +1,51 @@
+package org.gcube.portets.user.message_conversations.client;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.Random;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.datepicker.client.CalendarUtil;
+
+import gwt.material.design.client.constants.Color;
+
+public class Utils {
+ public static DateTimeFormat fmCurrentYear = DateTimeFormat.getFormat("MMM d H:mm");
+ public static DateTimeFormat fmPastYears = DateTimeFormat.getFormat("MMM d H:mm ''yy");
+
+ public static Color getRandomColor() {
+ Color toReturn = Color.values()[Random.nextInt(Color.values().length)];
+ while (toReturn.name().startsWith("WHITE") ||
+ toReturn.name().startsWith("GREY") ||
+ toReturn.name().startsWith("TRANSPARENT") ||
+ toReturn.name().startsWith("BROWN") ||
+ toReturn.name().contains("LIGHTEN") ||
+ toReturn.name().contains("ACCENT")) {
+ toReturn = Color.values()[Random.nextInt(Color.values().length)];
+ }
+ return toReturn;
+ }
+
+
+ public static boolean isMobile() {
+ int screenWidth = RootPanel.get(MessageConversations.DIV_CONTAINER_ID).getOffsetWidth();
+ return (screenWidth <= 768);
+ }
+
+ @SuppressWarnings("deprecation")
+ public static String getFormatteDate(Date date) {
+ Date now = new Date();
+ int dayInBetween = CalendarUtil.getDaysBetween(now, date);
+ switch (dayInBetween) {
+ case 0:
+ return"Today at " + (DateTimeFormat.getFormat("H:mm").format(date));
+ case -1:
+ return "Yesterday, " + (DateTimeFormat.getFormat("H:mm").format(date));
+ case -2:
+ return "2 days ago, " + (DateTimeFormat.getFormat("H:mm").format(date));
+ default:
+ return(now.getYear() == date.getYear()) ? fmCurrentYear.format(date) : fmPastYears.format(date);
+ }
+ }
+
+}
diff --git a/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java
new file mode 100644
index 0000000..1df4399
--- /dev/null
+++ b/src/main/java/org/gcube/portets/user/message_conversations/client/autocomplete/MaterialAutoComplete.java
@@ -0,0 +1,706 @@
+package org.gcube.portets.user.message_conversations.client.autocomplete;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.*;
+import com.google.gwt.event.logical.shared.*;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.*;
+import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
+import gwt.material.design.addins.client.MaterialAddins;
+import gwt.material.design.addins.client.autocomplete.constants.AutocompleteType;
+import gwt.material.design.addins.client.base.constants.AddinsCssName;
+import gwt.material.design.client.MaterialDesignBase;
+import gwt.material.design.client.base.*;
+import gwt.material.design.client.base.mixin.*;
+import gwt.material.design.client.constants.CssName;
+import gwt.material.design.client.constants.IconType;
+import gwt.material.design.client.constants.ProgressType;
+import gwt.material.design.client.ui.MaterialChip;
+import gwt.material.design.client.ui.MaterialLabel;
+import gwt.material.design.client.ui.html.Label;
+import gwt.material.design.client.ui.html.ListItem;
+import gwt.material.design.client.ui.html.UnorderedList;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+/**
+ *
+ * @author kevzlou7979
+ * @author gilberto-torrezan
+ * @author M. Assante, CNR-ISTI
+ * @see Material AutoComplete
+ */
+// @formatter:on
+public class MaterialAutoComplete extends AbstractValueWidget> implements HasPlaceholder,
+ HasProgress, HasType, HasSelectionHandlers, HasReadOnly {
+
+ static {
+ if (MaterialAddins.isDebug()) {
+ MaterialDesignBase.injectCss(MaterialAutocompleteDebugClientBundle.INSTANCE.autocompleteCssDebug());
+ } else {
+ MaterialDesignBase.injectCss(MaterialAutocompleteClientBundle.INSTANCE.autocompleteCss());
+ }
+ }
+
+ private Map suggestionMap = new LinkedHashMap<>();
+ private Label placeholderLabel = new Label();
+
+ private List itemsHighlighted = new ArrayList<>();
+ private FlowPanel panel = new FlowPanel();
+ private UnorderedList list = new UnorderedList();
+ private SuggestOracle suggestions;
+ private TextBox itemBox = new TextBox();
+ private SuggestBox suggestBox;
+ private int limit = 0;
+ private MaterialLabel errorLabel = new MaterialLabel();
+ private final ProgressMixin progressMixin = new ProgressMixin<>(this);
+
+ private String selectedChipStyle = "blue white-text";
+ private boolean directInputAllowed = true;
+ private MaterialChipProvider chipProvider = new DefaultMaterialChipProvider();
+
+ private final ErrorMixin errorMixin = new ErrorMixin<>(this, errorLabel, list, placeholderLabel);
+
+ private FocusableMixin focusableMixin;
+ private ReadOnlyMixin readOnlyMixin;
+
+ public final CssTypeMixin typeMixin = new CssTypeMixin<>(this, this);
+
+ /**
+ * Use MaterialAutocomplete to search for matches from local or remote data
+ * sources.
+ */
+ public MaterialAutoComplete() {
+ super(Document.get().createDivElement(), AddinsCssName.AUTOCOMPLETE, CssName.INPUT_FIELD);
+ add(panel);
+ }
+
+ public MaterialAutoComplete(AutocompleteType type) {
+ this();
+ setType(type);
+ }
+
+ public MaterialAutoComplete(String placeholder) {
+ this();
+ setPlaceholder(placeholder);
+ }
+
+ /**
+ * Use MaterialAutocomplete to search for matches from local or remote data
+ * sources.
+ *
+ * @see #setSuggestions(SuggestOracle)
+ */
+ public MaterialAutoComplete(SuggestOracle suggestions) {
+ this();
+ build(suggestions);
+ }
+
+ /**
+ * Generate and build the List Items to be set on Auto Complete box.
+ */
+ protected void build(SuggestOracle suggestions) {
+ list.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_LIST);
+ this.suggestions = suggestions;
+ final ListItem item = new ListItem();
+
+ item.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_INPUT_TOKEN);
+ suggestBox = new SuggestBox(suggestions, itemBox);
+ setLimit(this.limit);
+ String autocompleteId = DOM.createUniqueId();
+ itemBox.getElement().setId(autocompleteId);
+
+ item.add(suggestBox);
+ item.add(placeholderLabel);
+ list.add(item);
+
+ list.addDomHandler(event -> suggestBox.showSuggestionList(), ClickEvent.getType());
+
+ itemBox.addBlurHandler(blurEvent -> {
+ if (getValue().size() > 0) {
+ placeholderLabel.addStyleName(CssName.ACTIVE);
+ }
+ });
+
+ itemBox.addKeyDownHandler(event -> {
+ boolean changed = false;
+
+ switch (event.getNativeKeyCode()) {
+ case KeyCodes.KEY_ENTER:
+ if (directInputAllowed) {
+ String value = itemBox.getValue();
+ if (value != null && !(value = value.trim()).isEmpty()) {
+ gwt.material.design.client.base.Suggestion directInput = new gwt.material.design.client.base.Suggestion();
+ directInput.setDisplay(value);
+ directInput.setSuggestion(value);
+ changed = addItem(directInput);
+ if (getType() == AutocompleteType.TEXT) {
+ itemBox.setText(value);
+ } else {
+ itemBox.setValue("");
+ }
+ itemBox.setFocus(true);
+ }
+ }
+ break;
+ case KeyCodes.KEY_BACKSPACE:
+ if (itemBox.getValue().trim().isEmpty()) {
+ if (itemsHighlighted.isEmpty()) {
+ if (suggestionMap.size() > 0) {
+ ListItem li = (ListItem) list.getWidget(list.getWidgetCount() - 2);
+
+ if (tryRemoveSuggestion(li.getWidget(0))) {
+ li.removeFromParent();
+ changed = true;
+ }
+ }
+ }
+ }
+ case KeyCodes.KEY_DELETE:
+ if (itemBox.getValue().trim().isEmpty()) {
+ for (ListItem li : itemsHighlighted) {
+ if (tryRemoveSuggestion(li.getWidget(0))) {
+ li.removeFromParent();
+ changed = true;
+ }
+ }
+ itemsHighlighted.clear();
+ }
+ itemBox.setFocus(true);
+ break;
+ }
+
+ if (changed) {
+ ValueChangeEvent.fire(MaterialAutoComplete.this, getValue());
+ }
+ });
+
+ itemBox.addClickHandler(event -> suggestBox.showSuggestionList());
+
+ suggestBox.addSelectionHandler(selectionEvent -> {
+ Suggestion selectedItem = selectionEvent.getSelectedItem();
+ itemBox.setValue("");
+ if (addItem(selectedItem)) {
+ ValueChangeEvent.fire(MaterialAutoComplete.this, getValue());
+ }
+ itemBox.setFocus(true);
+ });
+
+ panel.add(list);
+ panel.getElement().setAttribute("onclick",
+ "document.getElementById('" + autocompleteId + "').focus()");
+ panel.add(errorLabel);
+ suggestBox.setFocus(true);
+ }
+
+ protected boolean tryRemoveSuggestion(Widget widget) {
+ Set> entrySet = suggestionMap.entrySet();
+ for (Entry entry : entrySet) {
+ if (widget.equals(entry.getValue())) {
+ if (chipProvider.isChipRemovable(entry.getKey())) {
+ suggestionMap.remove(entry.getKey());
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adding the item value using Material Chips added on auto complete box
+ */
+ public boolean addItem(final Suggestion suggestion) {
+ SelectionEvent.fire(MaterialAutoComplete.this, suggestion);
+ if (getLimit() > 0) {
+ if (suggestionMap.size() >= getLimit()) {
+ return false;
+ }
+ }
+
+ if (suggestionMap.containsKey(suggestion)) {
+ return false;
+ }
+
+ final ListItem displayItem = new ListItem();
+ displayItem.setStyleName(AddinsCssName.MULTIVALUESUGGESTBOX_TOKEN);
+
+ if (getType() == AutocompleteType.TEXT) {
+ suggestionMap.clear();
+ itemBox.setText(suggestion.getDisplayString());
+ } else {
+ final MaterialChip chip = chipProvider.getChip(suggestion);
+ if (chip == null) {
+ return false;
+ }
+
+ chip.addClickHandler(event -> {
+ if (chipProvider.isChipSelectable(suggestion)) {
+ if (itemsHighlighted.contains(displayItem)) {
+ chip.removeStyleName(selectedChipStyle);
+ itemsHighlighted.remove(displayItem);
+ } else {
+ chip.addStyleName(selectedChipStyle);
+ itemsHighlighted.add(displayItem);
+ }
+ }
+ });
+
+ if (chip.getIcon() != null) {
+ chip.getIcon().addClickHandler(event -> {
+ if (chipProvider.isChipRemovable(suggestion)) {
+ suggestionMap.remove(suggestion);
+ list.remove(displayItem);
+ itemsHighlighted.remove(displayItem);
+ ValueChangeEvent.fire(MaterialAutoComplete.this, getValue());
+ suggestBox.showSuggestionList();
+ }
+ });
+ }
+
+ suggestionMap.put(suggestion, chip);
+ displayItem.add(chip);
+ list.insert(displayItem, list.getWidgetCount() - 1);
+ }
+ return true;
+ }
+
+ /**
+ * Clear the chip items on the autocomplete box
+ */
+ public void clear() {
+ itemBox.setValue("");
+ placeholderLabel.removeStyleName(CssName.ACTIVE);
+
+ Collection values = suggestionMap.values();
+ for (Widget widget : values) {
+ Widget parent = widget.getParent();
+ if (parent instanceof ListItem) {
+ parent.removeFromParent();
+ }
+ }
+ suggestionMap.clear();
+
+ clearErrorOrSuccess();
+ }
+
+ @Override
+ protected FocusableMixin getFocusableMixin() {
+ if (focusableMixin == null) {
+ focusableMixin = new FocusableMixin<>(new MaterialWidget(itemBox.getElement()));
+ }
+ return focusableMixin;
+ }
+
+ /**
+ * @return the item values on autocomplete
+ * @see #getValue()
+ */
+ public List getItemValues() {
+ Set keySet = suggestionMap.keySet();
+ List values = new ArrayList<>(keySet.size());
+ for (Suggestion suggestion : keySet) {
+ values.add(suggestion.getReplacementString());
+ }
+ return values;
+ }
+
+ /**
+ * @param itemValues the itemsSelected to set
+ * @see #setValue(Object)
+ */
+ public void setItemValues(List itemValues) {
+ setItemValues(itemValues, false);
+ }
+
+ /**
+ * @param itemValues the itemsSelected to set
+ * @param fireEvents will fire value change event if true
+ * @see #setValue(Object)
+ */
+ public void setItemValues(List itemValues, boolean fireEvents) {
+ if (itemValues == null) {
+ clear();
+ return;
+ }
+ List list = new ArrayList<>(itemValues.size());
+ for (String value : itemValues) {
+ Suggestion suggestion = new gwt.material.design.client.base.Suggestion(value, value);
+ list.add(suggestion);
+ }
+ setValue(list, fireEvents);
+ if (itemValues.size() > 0) {
+ placeholderLabel.addStyleName(CssName.ACTIVE);
+ }
+ }
+
+ /**
+ * @return the itemsHighlighted
+ */
+ public List getItemsHighlighted() {
+ return itemsHighlighted;
+ }
+
+ /**
+ * @param itemsHighlighted the itemsHighlighted to set
+ */
+ public void setItemsHighlighted(List itemsHighlighted) {
+ this.itemsHighlighted = itemsHighlighted;
+ }
+
+ /**
+ * @return the suggestion oracle
+ */
+ public SuggestOracle getSuggestions() {
+ return suggestions;
+ }
+
+ /**
+ * Sets the SuggestOracle to be used to provide suggestions. Also setups the
+ * component with the needed event handlers and UI elements.
+ *
+ * @param suggestions the suggestion oracle to set
+ */
+ public void setSuggestions(SuggestOracle suggestions) {
+ this.suggestions = suggestions;
+ build(suggestions);
+ }
+
+ public void setSuggestions(SuggestOracle suggestions, AutocompleteType type) {
+ setType(type);
+ setSuggestions(suggestions);
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setLimit(int limit) {
+ this.limit = limit;
+ if (this.suggestBox != null) {
+ this.suggestBox.setLimit(limit);
+ }
+ }
+
+ /**
+ * Set the number of suggestions to be displayed to the user. This differs from
+ * setLimit() which set both the suggestions displayed AND the limit of values
+ * allowed within the autocomplete.
+ * @param limit
+ */
+ public void setAutoSuggestLimit(int limit) {
+ if (this.suggestBox != null) {
+ this.suggestBox.setLimit(limit);
+ }
+ }
+
+ @Override
+ public String getPlaceholder() {
+ return placeholderLabel.getText();
+ }
+
+ @Override
+ public void setPlaceholder(String placeholder) {
+ placeholderLabel.setText(placeholder);
+ }
+
+ /**
+ * Gets the current {@link MaterialChipProvider}. By default, the class uses
+ * an instance of {@link DefaultMaterialChipProvider}.
+ */
+ public MaterialChipProvider getChipProvider() {
+ return chipProvider;
+ }
+
+ /**
+ * Sets a {@link MaterialChipProvider} that can customize how the
+ * {@link MaterialChip} is created for each selected {@link Suggestion}.
+ */
+ public void setChipProvider(MaterialChipProvider chipProvider) {
+ this.chipProvider = chipProvider;
+ }
+
+ /**
+ * When set to false, only {@link Suggestion}s from the
+ * SuggestionOracle are accepted. Direct input create by the user is
+ * ignored. By default, direct input is allowed.
+ */
+ public void setDirectInputAllowed(boolean directInputAllowed) {
+ this.directInputAllowed = directInputAllowed;
+ }
+
+ /**
+ * @return if {@link Suggestion}s created by direct input from the user
+ * should be allowed. By default directInputAllowed is
+ * true.
+ */
+ public boolean isDirectInputAllowed() {
+ return directInputAllowed;
+ }
+
+ /**
+ * Sets the style class applied to chips when they are selected.
+ *
+ * Defaults to "blue white-text".
+ *
+ *
+ * @param selectedChipStyle The class or classes to be applied to selected chips
+ */
+ public void setSelectedChipStyle(String selectedChipStyle) {
+ this.selectedChipStyle = selectedChipStyle;
+ }
+
+ /**
+ * Returns the style class applied to chips when they are selected.
+ *
+ * Defaults to "blue white-text".
+ *
+ */
+ public String getSelectedChipStyle() {
+ return selectedChipStyle;
+ }
+
+ @Override
+ public void showProgress(ProgressType type) {
+ progressMixin.showProgress(ProgressType.INDETERMINATE);
+ }
+
+ @Override
+ public void setPercent(double percent) {
+ progressMixin.setPercent(percent);
+ }
+
+ @Override
+ public void hideProgress() {
+ progressMixin.hideProgress();
+ }
+
+ @Override
+ public HandlerRegistration addKeyUpHandler(final KeyUpHandler handler) {
+ return itemBox.addKeyUpHandler(event -> {
+ if (isEnabled()) {
+ handler.onKeyUp(event);
+ }
+ });
+ }
+
+ @Override
+ public void setType(AutocompleteType type) {
+ typeMixin.setType(type);
+ }
+
+ @Override
+ public AutocompleteType getType() {
+ return typeMixin.getType();
+ }
+
+ @Override
+ public HandlerRegistration addSelectionHandler(final SelectionHandler handler) {
+ return addHandler(new SelectionHandler() {
+ @Override
+ public void onSelection(SelectionEvent event) {
+ if (isEnabled()) {
+ handler.onSelection(event);
+ }
+ }
+ }, SelectionEvent.getType());
+ }
+
+ public ReadOnlyMixin getReadOnlyMixin() {
+ if (readOnlyMixin == null) {
+ readOnlyMixin = new ReadOnlyMixin<>(this, itemBox);
+ }
+ return readOnlyMixin;
+ }
+
+ @Override
+ public void setReadOnly(boolean value) {
+ getReadOnlyMixin().setReadOnly(value);
+ if (value) {
+ setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return getReadOnlyMixin().isReadOnly();
+ }
+
+ @Override
+ public void setToggleReadOnly(boolean toggle) {
+ getReadOnlyMixin().setToggleReadOnly(toggle);
+ }
+
+ @Override
+ public boolean isToggleReadOnly() {
+ return getReadOnlyMixin().isToggleReadOnly();
+ }
+
+ /**
+ * Interface that defines how a {@link MaterialChip} is created, given a
+ * {@link Suggestion}.
+ *
+ * @see MaterialAutoComplete#setChipProvider(MaterialChipProvider)
+ */
+ public static interface MaterialChipProvider {
+
+ /**
+ * Creates and returns a {@link MaterialChip} based on the selected
+ * {@link Suggestion}.
+ *
+ * @param suggestion the selected {@link Suggestion}
+ * @return the created MaterialChip, or null if the
+ * suggestion should be ignored.
+ */
+ MaterialChip getChip(Suggestion suggestion);
+
+ /**
+ * Returns whether the chip defined by the suggestion should be selected when the user clicks on it.
+ *
+ *
+ * Selecion of chips is used to batch remove suggestions, for example.
+ *
+ *
+ * @param suggestion the selected {@link Suggestion}
+ * @see MaterialAutoComplete#setSelectedChipStyle(String)
+ */
+ boolean isChipSelectable(Suggestion suggestion);
+
+ /**
+ * Returns whether the chip defined by the suggestion should be removed from the autocomplete when clicked on its icon.
+ *
+ *
+ * Override this method returning false to implement your own logic when the user clicks on the chip icon.
+ *
+ *
+ * @param suggestion the selected {@link Suggestion}
+ */
+ boolean isChipRemovable(Suggestion suggestion);
+ }
+
+ /**
+ * Default implementation of the {@link MaterialChipProvider} interface,
+ * used by the {@link MaterialAutoComplete}.
+ *
+ *
+ * By default all chips are selectable and removable. The default {@link IconType} used by the chips provided is the {@link IconType#CLOSE}.
+ *