Custom Resized Portlets using Sencha GXT 3.1 Beta

Sencha makes a container called the PortalLayoutContainer available through its API which allows the developer to add portlets to the container. The container extends the VerticalLayoutContainer and works by the user adding portlet widgets to the container in predefined column widths. The PLC doesn’t allow users to specify a grid with a predefined portlet height so that portlets dropped in will fit that size. Hence, we had some portlets that didn’t render nicely because they were too big for screens with low vertical resolutions. Also, because we don’t know what kind of screen resolution our users are going to use, we couldn’t just set a fixed height and forget about it.

In other words, sometimes one just wants to add a portlet that say, takes up 50% of the vertical screen real-estate rather than a fixed 450px. How did I solve this problem? Disclaimer: I admit that this solution is a bit of a hack, but without any other exposed API I did what I had to do.

Where's the rest of my portlet?
Where’s the rest of my portlet?

Solution: I had to resort to event handlers that listened for a resize of the PortalLayoutContainer. Essentially what this method does is traverse up the widget hierarchy until it finds a PortalLayoutContainer, then binds a resize handler for the specific portlet to the PLC. When the resize event is triggered on the PLC, each individual portlet is resized based on the initial parameter of how much screen real estate the portlet should take up. I also set a minimum size so that the portlets would not continue to resize if the overall PLC got too small (where the data started looking bad). You could easily set a maximum size as well so the portlets would not continue resizing after a certain point.

/**
	 * Attaches a resizeHandler to the parent (which is assumed to be a PortalLayoutContainer
	 * You need to call this method after the _portlet is added to the container
	 * @param width - the percentage of width of the window to resize to
	 * @param height - the percentage of height of the window to resize to
	 */
	public void bindResizeHandler(final double width, final double height) {
		if (_portlet != null) {
			_config.setResizable(Boolean.TRUE);
			
			//traverse up widget hierarchy until we reach PortalLayoutContainer
			Widget w = _portlet.getParent();
			while (w != null && !(w instanceof PortalLayoutContainer)) {
				w = w.getParent();
			}
			
			//Resize
			PortalLayoutContainer plc = ((PortalLayoutContainer) w);
			if (plc != null) {
				plc.addResizeHandler(new ResizeHandler () {

					@Override
					public void onResize(ResizeEvent event) {
						int newWidth = Math.max((int) (event.getWidth() * width) - 10, _config.getMinWidth());
						int newHeight = Math.max((int) (event.getHeight() * height) - 10, _config.getMinHeight());
						
						//Only resize if width or height > minWidth or minHeight - note min width doesn't quite work right
						//because GXT widgets still try to autoresize
						if (_config.getWidth() != newWidth || _config.getHeight() != newHeight) {
							_config.setWidth(newWidth);
							_config.setHeight(newHeight);
							resize(_config.getWidth(), _config.getHeight());
						}
					}
					
				});
			}
		}
	}
	
	@Override
	public void resize(int width, int height) {
		if (_config.getResizable()) {
			_portlet.setWidth(width);
			_portlet.setHeight(height);
			_portlet.forceLayout();
		}
	}

I know that this turns the portal container into more of a “dashboard,” but that’s what our requirements called for. If you want a portlet to have a fixed size, simply don’t bind the handler to that specific portlet and the height will remain fixed.

Here’s how I bound each portlet to the PLC during instantiation.

//a PLC is made with two columns, each 50% and one column that's 100% (a hack to get a 50/50/100 split
			_portalContainer = new PortalLayoutContainer(3);
			_portalContainer.setColumnWidth(0, .5);
			_portalContainer.setColumnWidth(1, .5);
			_portalContainer.setColumnWidth(2, 1);
			_updateList = new ArrayList<UpdateablePortlet>();

//A portlet config is my own object that has parameters like whether the portlet is resizable and options specific to the data used to populate the portlet
			PortletConfig _tjpConfig = new PortletConfig();
			_tjp = new TestJobsPortlet(_tjpConfig);
			_portalContainer.add(_tjp.asWidget(), 1);

//This will add the portlet to the top right corner of the PLC and bind a resize handler to take up 50% of the width and 50% of the height (i.e. 1/4 of the screen) whenever the PLC is resized
			_tjp.bindResizeHandler(.5, .5);

And here’s the result:

much better!
much better!
Advertisements

Custom axes with Sencha 3.1 GXT Charts

Here’s a short idiom for today. If you have a Sencha chart with multiple BarSeries, you can click on the legend by default, which will “exclude” certain values from the recalculation of the y-axis minimum and maximum. We found that there are certain times when Sencha will compute the max range of a chart as a value less than 10, while the axis has 10 tick marks. This leaves some ugly decimal points in the y-axis labels, which just doesn’t make sense in some instances (e.g. you can’t have a fraction of a whole object).

Bad! You can't have a fraction of a device!
Bad! You can’t have a fraction of a device!

In order to get whole numbers here, I added a legend handler which recomputes the axis and sets a minimum value if Sencha would compute a < 10 max for the y-axis. Here's the code snippet:

			legend.addLegendSelectionHandler(new LegendSelectionHandler() {

				@Override
				public void onLegendSelection(LegendSelectionEvent event) {
					if (axis.getTo() < 10) {
						axis.setMaximum(10.0);
					} else {
						axis.setMaximum(Double.NaN);
						axis.setInterval(Double.NaN);
					}
					axis.calcEnds();
					axis.drawAxis(false);
					_chart.redrawSurface();
				}
				
			});

And here's the result:

Screen Shot 2014-04-01 at 2.04.52 PM
Ahh, much better.

GWT/GXT Theming Part 2 – Adding Custom Appearance Classes on top of Sencha Themebuilder

Likely one of the first things you’ll encounter if using the themebuilder is how to create a custom style for something the themebuilder doesn’t support. In my adventures trying to build on top of Sencha’s themebuilder, it became apparent to me that they intended the tool to be used in a standalone fashion. That is, one runs the themer.sh or themer.bat file to generate the theme.jar file and you plug and play it in conjunction with GWT.

However, because the theme file is applied uniformly across your application, what happens if you don’t want buttons in one place to look like buttons in another? This came up when building our webapp, where we wanted our top-level nav to have one color and appearance on hover and other buttons within the application to have another color. As it turns out, there’s no way to specify this in the .theme file.

  • One set of buttons (rendered by the themebuilder)
  • Screen Shot 2014-02-27 at 2.25.46 PM

  • Another set of buttons (custom appearance)
  • Screen Shot 2014-02-27 at 2.29.29 PM

  • Yet another set of buttons (customer appearance)
  • Screen Shot 2014-02-27 at 2.30.20 PM

    So how did I do this? In fact, there isn’t an easy way currently. I had to go digging through the compiled appearance classes made by the themebuilder.jar file in search of an answer. Here’s an example of what the themebuilder spits out for a button made with your custom .theme file.

    Step 1: Create an custom appearance class for your button/widget that extends Sencha’s appearance class

    So the first step I found to creating custom styling on top of the themebuilder was to create my own appearance class. However, I didn’t want to start from scratch because Sencha already provides a good basis (e.g. by applying the fonts, default colors, creating sliced image files, etc). It’s true that a lot of these things go out of the window when creating your own appearance class and you can lose multi-browser support, but in our case, it was better than keeping all the buttons looking exactly the same.

    Here’s example of a custom appearance class for the dark grey buttons you see above.

    /**
     * HeaderButtonAppearance extends a theme appearance created by Sencha's
     * themebuilder.jar file.  It uses the gifs and base css created by Sencha's
     * themebuilder then adds additional css styling and modifies the rendering
     * logic to display header buttons.  
     */
    public class HeaderButtonAppearance<M> extends ButtonCellDefaultAppearance<M> {
    
      	final HeaderButtonStyle customStyle;
    	
    	/**
    	 * These are "resources" that comprise a ButtonCellAppearance
    	 * This interface describes the resources used by this button appearance.  This is not 
    	 * Sencha-specific.  I had originally tried extending the Css3ButtonResources class that 
             * the themebuilder spits out, but it required keeping the entire .css class in my package as well.
    	 */
      	public interface HeaderButtonResources extends ClientBundle {
      		@Source("HeaderButtonCell.css")
      		HeaderButtonStyle style();
      	}
      	
            //These methods are my new styles.
      	public interface HeaderButtonStyle extends CssResource {
      		String headerButton();
      		
      		String outerButton();
      	}
      	
      	public HeaderButtonAppearance() {
      		HeaderButtonResources headerResources = GWT.create(HeaderButtonResources.class);
      		customStyle = headerResources.style();
      		StyleInjectorHelper.ensureInjected(headerResources.style(), true);
      	}
      	
      	@Override
        public void render(ButtonCell<M> cell, Context context, M value, SafeHtmlBuilder sb) {
      	    Css3ButtonResources resources = GWT.create(Css3ButtonResources.class);
      	    Css3ButtonStyle style = resources.style();
                //Additional rendering stuff here, use style.____() to access the custom themebuilder styles
                //Sencha by default uses a two nested divs to render a button.  You may have to extract the precise
                //.class file from the theme.jar file to see how the html is constructed.
    
       }
    }
    
      There are a few quirks regarding this appearance class to be aware of:

    • In the *ButtonStyle interface, the string method maps to a css class name in the *ButtonResources interface css file.
    • The .css file that is used for the mapping must be in the same package as the appearance class
    • In order to use the styles made available through the compiled css and appearance classes built by the themebuilder, you have to instantiate a ClientBundle and access the css classes through the Css3ButtonStyle interface.
    • Because the default rendering of the button renders the themebuilder’s classes (e.g. for buttons, there are two nested divs, where the outer has .button {} applied and the inner has .buttonInner {} applied, I had to rewrite the render method myself. In my replacement, I swapped out the .button and .buttonInner classes for my own css classes. I maintain everything else.
    • I think there has to be a better way to do this. For example, I experimented with injecting html (an innermost div) into the cell which accepts only my css. That way, I could avoid having to @Override the render class and modify the html on the backend. However, it didn’t end up working very well because even with injecting my own css, I could not overwrite the styling applied to the parent divs. Ultimately, I think one could write a utility to go in and scrape out the other classes applied by the themebuilder, but this seemed like it would be a real hassle.

    Step 2: Define the .css classes in a file in the same package for your custom appearance

    Here’s an example of what my .css class looked like.

    .outerButton {
      border: none;
      background-color: #3892D3;
      background: transparent;
      padding: 4px;
      cursor: pointer;
      outline: none;
      padding-right:30px;
    }
    
    .headerButton {
      	padding: 0px;
      	text-align: left;
    	font-family: Arial, Helvetica, sans-serif;  /* actual font is not arial */
    	font-size: 17px;
    }
    
    .headerButton:hover {
    	color: white;
    }
    

    If you look at my HeaderButtonStyle class, you’ll notice that the css class names map directly to the functions in the interface. GWT takes care of the actual mapping of values. These are the classes that I can use to style my widget and are accessible via my instance of the HeaderButtonStyle. Here’s an example of how I render the HeaderStyleButton:

    
    @Override
        public void render(ButtonCell<M> cell, Context context, M value, SafeHtmlBuilder sb) {
                //The default style CSS Resource in case I need to access styles built by the themebuilder
    
      	    Css3ButtonResources resources = GWT.create(Css3ButtonResources.class);
      	    Css3ButtonStyle style = resources.style();
    
                //Begin actual construction of the button widget
      		
      	    String constantHtml = cell.getHTML();
      	    boolean hasConstantHtml = constantHtml != null && constantHtml.length() != 0;
    
      	    boolean isBoolean = value != null && value instanceof Boolean;
    
      	    String text = hasConstantHtml ? cell.getText() : (value != null && !isBoolean)
      	        ? SafeHtmlUtils.htmlEscape(value.toString()) : "";
    
      	    String scaleClass = "";
      	    String iconClass = "";
      	    String additionalCssClasses = customStyle.headerButton();
    
                //Size-dependent styling
      	    ButtonScale scale = cell.getScale();
    
      	    switch (scale) {
      	      case SMALL:
      	        scaleClass = style.small();
      	        break;
    
      	      case MEDIUM:
      	        scaleClass = style.medium();
      	        break;
    
      	      case LARGE:
      	        scaleClass = style.large();
      	        break;
      	    }
    
                //Outer button class, you could add anything else you want here
      	    String buttonClass = customStyle.outerButton();
    
      	    boolean hasText = text != null && !text.equals("");
      	    if (!hasText) {
      	      buttonClass += " " + style.noText();
      	    }
     
                //The css classes applied to the inner div
                String innerClass = "";
      	    innerClass += " " + scaleClass;
      	    innerClass += " " + additionalCssClasses;
    
      	    SafeHtmlBuilder builder = new SafeHtmlBuilder();
    
      	    builder.appendHtmlConstant("<div class='" + buttonClass + "'>");
      	    builder.appendHtmlConstant("<div class='" + innerClass + "'>");
    
      	    builder.appendHtmlConstant(text);
      	    builder.appendHtmlConstant("</div></div>");
      	    sb.append(builder.toSafeHtml());
      	  }
    
    

    Step 3: Instantiate your widget using your custom appearance.

    TextButton button = new TextButton(new TextButtonCell(
            			new HeaderButtonAppearance<String>()), "Text for Button");
    

    You can see that when I instantiate a button, I pass in an appearance class instantiated with the type of value that the button receives (a string in this case). If you look at the prebuilt java classes (e.g. Css3ButtonCellAppearance) from the themebuilder, you’ll notice that the default constructor with no parameters will instantiate a cell with the default appearance. That’s why it’s necessary to use the constructor where you pass in the actual custom appearance class.

    Custom styling and appearance classes on top of Sencha themebuilder GXT 3.1 (Part 1 – Using the Themebuilder)

    I must find somewhere for my collected knowledge on the “joy” of using Sencha themebuilder. At my company, we’re currently working on building a custom style for our web application. Somewhere in the past, the decision was made to use Sencha GXT for their custom widgets and styling on top of GWT. Like most frameworks, things went smoothly as long as we were using the functionality out of the box. However, when we tried venturing outside, we were quickly reprimanded by the limitations of the framework.

    In our scenario, we needed to brand our application differently than one of the built-in Sencha themes. Fortunately for us, or so we thought, Sencha created a nice tool in the knick-of-time called the “themebuilder” (currently in beta as of 2/25/2014). The themebuilder alleviates some pains of styling if you stay strictly within the guardrails, but quickly becomes inadequate if you need to do styling on top of it.

    Part A: About the themebuilder and running it.

      Running it is easy (just make sure you are using java version 1.7+). On a mac:

    • You run ./themer.sh with certain command line options. The themer script reads in a custom Sencha file (*.theme) which it then uses to compile and build a set of appearance classes which GWT accepts. Here’s an example of the command I used:
    • ./themer.sh ../examples/mytheme/mytheme.theme
    • As of the beta 2/25/2014, the running the themer will by default override the .jar file.

    • The .theme file is a huge pain to work with. It’s Sencha’s own proprietary format. It’s currently not well documented, which makes it very difficult to know how certain values within the .theme file map to Sencha widgets and how these get mapped on top of GWT and then what eventually gets spat out in your browser. Here’s a snippet:
      theme {
        /* First, create a name for your theme, and define a package to place it in */
        name = "myCustomTheme"
        basePackage = "com.example"
      
        /* Next, configure these basic defaults, to be used throughout the file */
        text = util.fontStyle("Arial, Helvetica, sans-serif", "12px", "#000000", "normal")
        textWhite = util.fontStyle("Arial, Helvetica, sans-serif", "12px", "#eeeeee", "normal")
      
        borderRadius = 6
      
        bgColor = "#ffffff"
        headerBgColor = "#666666"
        menuHoverColor = "#e7e7e7"
        menuSelectColor = "#b7daff"
        headerBgColorLight = "#f9f9f9"
      
        iconColor = "#777777"
        ...
      }
      

      You can see that the file is not quite json, java, or css. It’s some mishmash of everything, but it allows you to define variables and reference them later on in the .theme. This is Sencha’s claim for the themebuilders utility. By simply changing a few values (e.g. the background color, font color, font text, etc.), you can change the entire look and feel of your web application. That is all fine and dandy, but what is not described is what things you actually can and can’t change. For example, here’s a list of things I found that the themebuilder is not capable of:

      • Styling a widget in one instance different from another
      • Adding custom fonts
      • Styling nested widgets differently from non-nested widget (e.g. a button in one panel looking different than a button in another)
      • Support for icons libraries like font-awesome or glyphicon
      • Styling text within a toolbar (the LabelToolItem widget)
      • Styling the body content various content panels widgets (ContentPanel, FramedPanel, AccordionPanelAppearance, Window, Dialog)
      • Stylizing a button when its menu is open vs. closed

      In the next series of posts, I’ll be showing you how I solved each of these issues. Stay tuned for more.

      A general tip: When modifying the .theme file, I used sublime text and had the syntax set to “Javascript.” This enabled some basic coloring and recognition of comments and strings. The coloring was very helpful.

      Part B: Applying the custom theme on top of your GWT project
      This part is more specific to GWT users, but may be helpful to some. I’m currently working in a build environment using Java 1.7, Eclipse Version: Kepler Service Release 1, Build id: 20130919-0819, GWT plugin version 1.8.9 to Eclipse, and maven. Assuming that you’re already configured and up and running with GXT and GWT, you should have an App.gwt.xml file that looks something like this:

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.5.1//EN"
        "http://google-web-toolkit.googlecode.com/svn/tags/2.5.1/distro-source/core/src/gwt-module.dtd">
      
      <module rename-to='mymodule'>
      ...
      	<!-- GXT Theme -->
       	<inherits name='com.example.Theme' /> 
      ...
      </module>
      

      The key line is the GXT Theme line where GWT applies a theme on top of all of its widgets. This theme name should follow the convention set forth in your .theme file.

        name = "myCustomTheme"
        basePackage = "com.example"
      

      After you build your theme.jar file using the custom .theme file (I used the skeleton.theme file included which has all the values in it), you need to add the jar to your buildpath (or to maven as a dependency). I found that the fastest workflow for me was to modify the .theme file, use an html tool that Sencha includes to approximate the styles, and have my buildpath point directly to the .jar file where it’s created by the themebuilder. For more on this html tool (which helps speed up theme creation), see this post.

      Edit: The new beta themebuilder (released by Sencha on 2/25/2014) spits out some instructions on how to do this, along with the context name of your theme.jar file.

      Here’s a sample:

      The customTheme theme has finished generating.
      Copy the jar (/path/to/theme/customTheme.jar) into your project and copy the following line into your gwt.xml file:
      
          <inherits name="com.example.Theme" />
      

      Then, I run GWT Server through my eclipse in debug mode, which picks up the changes to the theme. Because GWT is trying to rapidly render and compile all of the javascript files and apply the custom theme, it tends to operate very slowly.

      Hopefully if everything went according to play, you should see a different style on top of your application. In the next posts, I’ll start getting into customizing the theme and some workarounds for its deficiencies.