/* * Copyright (c) 2009-2011 Lockheed Martin Corporation * * 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.eurekastreams.web.client.ui.common.stream; import org.eurekastreams.server.action.request.stream.PostActivityRequest; import org.eurekastreams.server.domain.DomainConversionUtility; import org.eurekastreams.server.domain.EntityType; import org.eurekastreams.server.domain.stream.ActivityDTO; import org.eurekastreams.server.domain.stream.StreamScope; import org.eurekastreams.web.client.events.EventBus; import org.eurekastreams.web.client.events.MessageAttachmentChangedEvent; import org.eurekastreams.web.client.events.MessageStreamAppendEvent; import org.eurekastreams.web.client.events.MessageTextAreaChangedEvent; import org.eurekastreams.web.client.events.Observer; import org.eurekastreams.web.client.events.data.GotSystemSettingsResponseEvent; import org.eurekastreams.web.client.events.errors.ErrorPostingMessageToNullScopeEvent; import org.eurekastreams.web.client.model.ActivityModel; import org.eurekastreams.web.client.model.SystemSettingsModel; import org.eurekastreams.web.client.ui.Session; import org.eurekastreams.web.client.ui.common.avatar.AvatarWidget; import org.eurekastreams.web.client.ui.common.avatar.AvatarWidget.Size; import org.eurekastreams.web.client.ui.common.stream.attach.Attachment; import org.eurekastreams.web.client.ui.common.stream.attach.bookmark.AddLinkComposite; import org.eurekastreams.web.client.ui.common.stream.decorators.ActivityDTOPopulator; import org.eurekastreams.web.client.ui.common.stream.decorators.ActivityDTOPopulatorStrategy; import org.eurekastreams.web.client.ui.common.stream.decorators.object.NotePopulator; import org.eurekastreams.web.client.ui.common.stream.decorators.verb.PostPopulator; import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; /** * Post a message to the stream. */ public class PostToStreamComposite extends FlowPanel { /** Default text to show when post box is empty. */ private static final String DEFAULT_POST_BOX_DEFAULT_TEXT = "Something to share?"; /** Text to show when post box is empty. */ private final String postBoxDefaultText; /** Maximum length of a message. */ private static final int MAX_MESSAGE_LENGTH = 250; /** The refresh interval in milliseconds. */ private static final int REFRESH_INTERVAL = 250; /** The number of characters remaining. */ private Label charsRemaining; /** The post button. */ private Label postButton; /** The message. */ private PostToStreamTextboxPanel message; /** The list of links in the message. */ private AddLinkComposite links; /** The post to panel. */ private PostToPanel postToPanel; /** Container for content warning. */ private final FlowPanel contentWarningContainer = new FlowPanel(); /** The content warning. */ private Label contentWarning; /** error label. */ private final Label errorMsg = new Label(); /** panel holding controls below the post box. */ private final FlowPanel expandedPanel = new FlowPanel(); /** Current text in the message box. */ private String messageText = ""; /** The link information. */ private Attachment attachment; /** The last fetched link. */ private String lastFetched = ""; /** The link text. */ private final String linkText = ""; /** Activity Populator. */ private final ActivityDTOPopulator activityPopulator = new ActivityDTOPopulator(); /** * Constructor. * * @param inScope * the scope. */ public PostToStreamComposite(final StreamScope inScope) { this(inScope, DEFAULT_POST_BOX_DEFAULT_TEXT); } /** * Constructor. * * @param inScope * the scope. * @param inPostBoxDefaultText * Text to show when box is empty. */ public PostToStreamComposite(final StreamScope inScope, final String inPostBoxDefaultText) { postBoxDefaultText = inPostBoxDefaultText; setupWidgets(inScope); setupEvents(); SystemSettingsModel.getInstance().fetch(null, true); onRemainingCharactersChanged(); } /** * Builds the UI. * * @param inScope * the scope. */ private void setupWidgets(final StreamScope inScope) { this.getElement().setAttribute("id", "post-to-stream"); this.addStyleName(StaticResourceBundle.INSTANCE.coreCss().small()); charsRemaining = new Label(); postButton = new Label("Post"); message = new PostToStreamTextboxPanel(); message.setText(postBoxDefaultText); message.setVisible(false); // Hide until post ready event. this.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postToStream()); errorMsg.addStyleName(StaticResourceBundle.INSTANCE.coreCss().formErrorBox()); errorMsg.setVisible(false); this.add(errorMsg); FlowPanel postInfoContainer = new FlowPanel(); postInfoContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postInfoContainer()); postButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postButton()); postInfoContainer.add(postButton); charsRemaining.addStyleName(StaticResourceBundle.INSTANCE.coreCss().charactersRemaining()); postInfoContainer.add(charsRemaining); AvatarWidget avatar = new AvatarWidget(Session.getInstance().getCurrentPerson(), EntityType.PERSON, Size.VerySmall); avatar.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postEntryAvatar()); Panel entryPanel = new FlowPanel(); entryPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postEntryPanel()); entryPanel.add(avatar); entryPanel.add(postInfoContainer); entryPanel.add(message); SimplePanel breakPanel = new SimplePanel(); breakPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().breakClass()); entryPanel.add(breakPanel); add(entryPanel); // below text area: links and post to on one line, then content warning below expandedPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().postExpandedPanel()); postToPanel = new PostToPanel(inScope); expandedPanel.add(postToPanel); links = new AddLinkComposite(); expandedPanel.add(links); contentWarning = new InlineLabel(); contentWarningContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().contentWarning()); contentWarningContainer.add(new SimplePanel()); contentWarningContainer.add(contentWarning); expandedPanel.add(contentWarningContainer); add(expandedPanel); setVisible(false); setVisible(Session.getInstance().getCurrentPerson() != null); } /** * Wires up events. */ private void setupEvents() { // user clicked in message text box message.addFocusHandler(new FocusHandler() { public void onFocus(final FocusEvent inEvent) { if ((" " + getStyleName() + " ").contains(StaticResourceBundle.INSTANCE.coreCss().small())) { removeStyleName(StaticResourceBundle.INSTANCE.coreCss().small()); onExpand(); } } }); message.addValueChangedHandler(new ValueChangeHandler<String>() { public void onValueChange(final ValueChangeEvent<String> newValue) { checkMessageTextChanged(); } }); // user typed in message text box message.addKeystrokeHandler(new KeyUpHandler() { public void onKeyUp(final KeyUpEvent ev) { if (ev.getNativeKeyCode() == KeyCodes.KEY_ENTER && ev.isControlKeyDown() && message.getText().length() > 0) { checkMessageTextChanged(); handlePostMessage(); } else { checkMessageTextChanged(); } } }); // user clicked on post button postButton.addClickHandler(new ClickHandler() { public void onClick(final ClickEvent ev) { checkMessageTextChanged(); handlePostMessage(); } }); final EventBus eventBus = Session.getInstance().getEventBus(); eventBus.addObserver(MessageStreamAppendEvent.class, new Observer<MessageStreamAppendEvent>() { public void update(final MessageStreamAppendEvent event) { errorMsg.setVisible(false); addStyleName(StaticResourceBundle.INSTANCE.coreCss().small()); messageText = ""; message.setText(postBoxDefaultText); onRemainingCharactersChanged(); links.close(); } }); eventBus.addObserver(GotSystemSettingsResponseEvent.class, new Observer<GotSystemSettingsResponseEvent>() { public void update(final GotSystemSettingsResponseEvent event) { String warning = event.getResponse().getContentWarningText(); if (warning != null && !warning.isEmpty()) { contentWarning.setText(warning); } else { contentWarning.setVisible(false); } message.setVisible(true); } }); eventBus.addObserver(MessageTextAreaChangedEvent.getEvent(), new Observer<MessageTextAreaChangedEvent>() { public void update(final MessageTextAreaChangedEvent arg1) { // the value changed - make sure we're not stuck in the disabled, non-editable mode if ((" " + getStyleName() + " ").contains(StaticResourceBundle.INSTANCE.coreCss().small())) { removeStyleName(StaticResourceBundle.INSTANCE.coreCss().small()); } onRemainingCharactersChanged(); } }); eventBus.addObserver(MessageAttachmentChangedEvent.class, new Observer<MessageAttachmentChangedEvent>() { public void update(final MessageAttachmentChangedEvent evt) { errorMsg.setVisible(false); attachment = evt.getAttachment(); if (attachment == null && messageText.isEmpty()) { hidePostButton(); } else { showPostButton(); } } }); eventBus.addObserver(new ErrorPostingMessageToNullScopeEvent(), new Observer<ErrorPostingMessageToNullScopeEvent>() { public void update(final ErrorPostingMessageToNullScopeEvent event) { errorMsg.setText(event.getErrorMsg()); errorMsg.setVisible(true); showPostButton(); } }); } /** * Invoked when the composite is expanded (due to user clicking on the box). */ protected void onExpand() { message.setText(""); } /** * Sets up the magic show/hide for the publisher. */ public void setUpMinimizer() { nativeSetUpMinimizer(postBoxDefaultText); } /** * Sets up the magic show/hide for the publisher. * * @param inDefaultMessage * Message to display when box is empty. */ private static native void nativeSetUpMinimizer(final String inDefaultMessage) /*-{ $wnd.defaultMessage = inDefaultMessage; $wnd.overPoster = false; $doc.onmousedown = function() { if(!$wnd.overPoster && $wnd.jQuery(".post-button").is(".inactive")) { setTimeout(function() { $wnd.jQuery('#post-to-stream').addClass('small'); },500); setTimeout(function() { $wnd.jQuery('#post-to-stream textarea').val($wnd.defaultMessage); },500); } else if($wnd.overPoster && $wnd.jQuery("#post-to-stream").is(".small")) { $wnd.jQuery('#post-to-stream textarea').focus(); } }; $wnd.jQuery("#post-to-stream").hover( function() { $wnd.overPoster = true; }, function() { $wnd.overPoster = false; }); }-*/; /** * Handle the post activity action - triggered by CONTROL-ENTER or clicking the Post button. */ private void handlePostMessage() { if (!postButton.getStyleName().contains("inactive") && messageText.length() <= MAX_MESSAGE_LENGTH && (!messageText.isEmpty() || attachment != null)) { hidePostButton(); postMessage(); lastFetched = ""; } } /** * Get the scope. * * @return the scope. */ public StreamScope getScope() { return postToPanel.getPostScope(); } /** * Set the scope. * * @param inScope * the scope. */ public void setScope(final StreamScope inScope) { postToPanel.setPostScope(inScope); } /** * Determine if message text changed and handle appropriately. */ private void checkMessageTextChanged() { String newText = message.getText(); if (!newText.equals(messageText)) { messageText = newText; Session.getInstance().getEventBus().notifyObservers(MessageTextAreaChangedEvent.getEvent()); } } /** * Sets the remaining number of characters. */ private void onRemainingCharactersChanged() { final int textLength = messageText.length(); charsRemaining.setText(Integer.toString(MAX_MESSAGE_LENGTH - textLength)); if (textLength <= MAX_MESSAGE_LENGTH && (!messageText.isEmpty() || links.hasAttachment())) { showPostButton(); } else { hidePostButton(); } if (textLength > MAX_MESSAGE_LENGTH) { charsRemaining.addStyleName(StaticResourceBundle.INSTANCE.coreCss().overCharacterLimit()); } else { charsRemaining.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().overCharacterLimit()); } } /** * Hides the post button. */ private void hidePostButton() { postButton.addStyleName(StaticResourceBundle.INSTANCE.coreCss().inactive()); } /** * Displays the post button. */ private void showPostButton() { postButton.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().inactive()); } /** * @return The user-entered text. */ protected String getMesssageText() { return messageText; } /** * @return The panel below the text entry box. */ protected FlowPanel getSubTextboxPanel() { return expandedPanel; } /** * Posts the message. */ public void postMessage() { StreamScope scope = postToPanel.getPostScope(); if (scope == null) { ErrorPostingMessageToNullScopeEvent error = new ErrorPostingMessageToNullScopeEvent(); error.setErrorMsg("The stream name you entered could not be found"); Session.getInstance().getEventBus().notifyObservers(error); return; } EntityType recipientType = DomainConversionUtility.convertToEntityType(scope.getScopeType()); if (EntityType.NOTSET.equals(recipientType)) { recipientType = EntityType.GROUP; } ActivityDTOPopulatorStrategy objectStrat = attachment != null ? attachment.getPopulator() : new NotePopulator(); ActivityDTO activity = activityPopulator.getActivityDTO(messageText, recipientType, scope.getUniqueKey(), new PostPopulator(), objectStrat); PostActivityRequest postRequest = new PostActivityRequest(activity); ActivityModel.getInstance().insert(postRequest); } }