GST Site Foundation Mobile

The mobile project helps to build multi-device websites on a single infrastructure Sites. It allows for editors to select a single layout (template) for an asset, and gives a developer the control to build device-specific Templates if needed. This can be used in conjunction with Responsive Design techniques.

The basic premise here is that the device specific parts of the website can be grouped at a high level, either for a desktop, a mobile or a tablet. The consequence is that device detection can be grouped into 3 families. The idea is that an editor selects a generic LayoutTemplate and that a developer implements device specific templates, LayoutTemplate_desktop, LayoutTemplate_mobile and LayoutTemplate_tablet. If a device-specific template is not found the original template will be used.

The algorithm used for the device detection is from mobiforge's PHP implementatation. Other implementation might use WURFL or you can roll your own.

In device specific template you can do your device specific code, like

  • load the navigation tree for mobile sites
  • load additional javascript for mobile or tablet, or use that big script just for desktop browsers.
  • load specific style sheets that cannot be handled through Responsive Design techniques.
  • Load device specific content, beyond just device specific formatting.

The idea is that within the same editorial site content for specific devices can be managed.

Below is an Action that renders a page for a specific device based on 'normal' GSF dispatching rules. This Action is to be used in a Controller (ie Wrapper/Dispatcher). As you can see the implementation overwrites 3 methods of the RenderPageAction, one is to select to correct translation for the requested assets, and the other 2 to change the editor selected template to a device specific template.

package com.fatwire.gst.foundation.mobile.action;
/**
 * RenderPage action extension that handles translations as well as device
 * detection to direct the visitor to the device specific template. </p> The
 * rule is straight forward. If the template is X then a lookup is done if there
 * is a Template with the name X_mobile and that is used if the visitor is using
 * a mobile device. The same for _desktop and _tablet. If such a template does
 * not exist, the 'normal' template is used. </p>
 * 
 * 
 * @author Dolf Dijkstra
 * 
 */
public class DeviceAwareRenderPageAction extends RenderPage {

    @InjectForRequest
    public DeviceDetector detector;

    @InjectForRequest
    public LocaleService localeService;

    @Override
    protected AssetIdWithSite resolveAssetId() {
        return findTranslation(super.resolveAssetId());
    }

    @Override
    protected void callTemplate(AssetIdWithSite id, String tname) {
        DeviceType type = detector.detectDeviceType(ics);
        if (LOG.isDebugEnabled())
            LOG.debug("detected device type: " + type);
        String dtname = checkForDeviceTName(id, tname, type);
        super.callTemplate(id, dtname);

    }

    @Override
    protected void callPage(AssetIdWithSite id, String pagename, String packedArgs) {
        DeviceType type = detector.detectDeviceType(ics);
        if (LOG.isDebugEnabled())
            LOG.debug("detected device type: " + type);
        String nn = checkForDevicePagename(id, pagename, type);
        super.callPage(id, nn, packedArgs);
    }

    protected AssetIdWithSite findTranslation(AssetIdWithSite id) {
        AssetIdWithSite n = id;

        DimensionFilterInstance df = localeService.getDimensionFilter(id.getSite());
        if (df != null) {
            AssetId translated = localeService.findTranslation(id, df);
            if (translated != null)
                n = new AssetIdWithSite(translated, id.getSite());

        }
        return n;
    }

    protected String getPostfix(DeviceType type) {
        switch (type) {
            case MOBILE:
                return "_mobile";
            case TABLET:
                return "_tablet";
            default:
                return "_desktop";
        }

    }

    protected String checkForDeviceTName(AssetIdWithSite id, String tname, DeviceType type) {
        if (StringUtils.endsWith(tname, "_mobile") || StringUtils.endsWith(tname, "_tablet")
                || StringUtils.endsWith(tname, "_desktop")) {
            return tname;
        }
        String pf = getPostfix(type);
        final String targetPagename = tname.startsWith("/") ? (id.getSite() + tname + pf) : (id.getSite() + "/"
                + id.getType() + "/" + tname + pf);
        try {
            if (ics.getPageData(targetPagename).isRegistered()) {
                return tname + pf;
            } else if (LOG.isTraceEnabled()) {
                log.trace("There is no special template for " + type + " at template " + tname);
            }
        } catch (IllegalArgumentException e) {
            LOG.warn(e.getMessage());
            // ignore
        }

        return tname;
    }

    protected String checkForDevicePagename(AssetIdWithSite id, String pagename, DeviceType type) {
        if (StringUtils.endsWith(pagename, "_mobile") || StringUtils.endsWith(pagename, "_tablet")
                || StringUtils.endsWith(pagename, "_desktop")) {
            return pagename;
        }
        String pf = getPostfix(type);
        final String targetPagename = pagename + pf;

        try {

            if (ics.getPageData(targetPagename).isRegistered()) {
                return targetPagename;
            } else if (LOG.isTraceEnabled()) {
                log.trace("There is no device specific pagename for " + type + " at page " + pagename);
            }
        } catch (NullPointerException e) {
            // ignore
        } catch (IllegalArgumentException e) {
            LOG.warn(e.getMessage());
            // ignore
        }
        return pagename;
    }
}

The device detection service needs to be added to the ObjectFactory chain. This is done by adding (or modifying) the file WEB-INF/gsf-groovy/gsf/ObjectFactory.groovy with the content below.

package gsf

import COM.FutureTense.Interfaces.ICS

import com.fatwire.gst.foundation.controller.action.Factory
import com.fatwire.gst.foundation.mobile.DeviceDetector
import com.fatwire.gst.foundation.mobile.mobiforge.MobiForgeDeviceDetector
import com.fatwire.gst.foundation.controller.annotation.ServiceProducer

public class ObjectFactory {

  @ServiceProducer(cache = true)
  static DeviceDetector createDeviceDetector(ICS ics, Factory f){
     return new MobiForgeDeviceDetector()
  }
}