Thursday, November 18, 2010

That Video is Too Small!

So your portlet that shows videos works great, you keep replaying it over and over 'cause you're proud of it, and your boss thinks it's pretty cool except...

"... it's so small... Can't really even see those Flash controls. Any way to have the user be able to make it bigger, like maybe full screen sized as when they click to maximize the portlet? That way it can be small and out of the way when they aren't watching it!"

Short answer: Yes.

In Liferay you can maximize a portlet to make it easier to see or more usable. The problem is maximizing the portlet makes the PORTLET bigger but that Flash video isn't going to grow with it. What you need is code that will cause the video to grow with the portlet.

Fortunately, this IS possible. It's easy, too.

First, take a look at your HTML code for your object. (In this case, it's an ice:outputMedia control.) If you don't have it already you're going to need a style attribute to set the width and height of your video. Usually it looks something like style="width: 300px; height: 300px;"

If you don't have that, add it. If you have it, change it to look like this:

style="width: #{videoDisplay.width}px; height: #{videoDisplay.height}px;"

Yes, we're going to do this with JSF/ICEfaces. If you're not using that tech then just substitute whatever JSP tag you want that'll make those values dynamic.

Next, you're going to need to add some members to your backing bean.

private int width, height;
private boolean maximized;
private static int MAX_WIDTH = 900;
private static int MAX_HEIGHT = 900;
private static int MIN_WIDTH = 300;
private static int MIN_HEIGHT = 300;

and some setters and getters for those non-static ints.

public void setMaximized(boolean maximized) {
if(maximized){
width = MAX_WIDTH;
height = MAX_HEIGHT;
}
else{
width = MIN_WIDTH;
height = MIN_HEIGHT;
}
this.maximized = maximized;
}

public boolean isMaximized() {

return maximized;
}

public void setWidth(int width) {
this.width = width;
}

public int getWidth() {
return width;
}

public void setHeight(int height) {
this.height = height;
}

public int getHeight() {
return height;
}

"But wait!" You say. "There's actual processing being done in your setter! Isn't that bad practice?"

Some say it is, some say it isn't. Know what I say? Whatever makes the code cleaner and simpler. You'll see the method (pardon the pun) to my madness in a moment.

Make sure you initialize your width and height too, by the way. Either put it in your backing bean's constructor or init() method or wherever you set things up.

if(this.isMaximized()){
width = MAX_WIDTH;
height = MAX_HEIGHT;
}
else{
width = MIN_WIDTH;
height = MIN_HEIGHT;
}

Now, the tricky part is that we want the maximization to be determined before the page renders. That means you're going to need to build a phase listener class if you don't have one already. Here's a basic one:

package com.myproject.listener;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.render.ResponseStateManager;
import javax.portlet.PortletPreferences;
import javax.portlet.RenderRequest;
import javax.portlet.WindowState;

public class VideoPhaseListener implements PhaseListener {

private static final long serialVersionUID = 1L;

public void afterPhase(PhaseEvent pe) {
//some implementation
}

public void beforePhase(PhaseEvent pe) {

VideoDisplay videoDisplay = (VideoDisplay) facesContext
.getApplication().getELResolver().getValue(
facesContext.getELContext(), null, "videoDisplay");

if (pe.getPhaseId() == PhaseId.RENDER_RESPONSE) {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
RenderRequest renderRequest = (RenderRequest) externalContext
.getRequest();
PortletPreferences prefs = renderRequest.getPreferences();
videoDisplay.setMaximized((renderRequest.getWindowState().equals(WindowState.MAXIMIZED)));
}
}

public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}

See what's happening? Before the Render Response phase we're getting the portlet state from the Render Request. So if the user has clicked the maximize button, the state is MAXIMIZED and we want the backing bean to use the maximized constant values we set up earlier. Otherwise we want the minimums.

See now why our setMaximized() setter had that bit of processing? When we change the value of maximized in the backing bean we want to immediately change the width and height to match. Could I have used the setWidth() and setHeight() methods here and set them all from the Phase Listener? Well yeah I could have but this way is cleaner and makes the backing bean (videoDisplay) manage itself. It isn't the job of the Phase Listener to handle the details of what goes on in the backing bean. Its job here is to report the state of the portlet. Period.

One last step: Make sure you configure your new listener in faces-config.xml.

<lifecycle>
<phase-listener>
com.myproject.listener.VideoPhaseListener
</phase-listener>
</lifecycle>

So when the page response is rendered, it'll use the values appropriate for the window state.

Now, you could take this a step further and have those static width and height values come in from some sort of configuration and avoid hard coding, but you get the idea.

No comments:

Post a Comment