Skip to main content

Session Notes - Not Your Father's Custom Tags

Presenter: Dave Klein

Custom Tags are Valuable

  • build reusable components
  • keep code out of pages
  • make pages more readable
    • even putting presentation/html code into custom tags can make the page more readable
  • encapsulate domain knowledge
    • easy for page designers to use without knowing much about the domain model, etc.

JSP Custom Tags are Painful

  • create handler class for each tag
  • implement one of several interfaces
  • implement interface's methods
  • <pure_evil>define a TLD</pure_evil>
  • add a page directive for each TLD
  • great once they exist, but because they're a pain to create people avoid them and don't get the benefit
  • JSP custom tag for hello wolrd is 2+ pages of code, equivalent GSP tag is about 6 lines

The Power of JSP Without the Pain

  • convention over configuration
  • no tld, no xml
  • no interfaces to implement
  • a TagLibrary is a single groovy class
    • can contain multiple tags within this class
  • each tag is a closure
  • don't need to declare within the page
    • if it's in the project, it's available on every page

GSP: A Quick-Start Guide

What you can do in a GSP tag

  • accept and use attributes
  • accept and conditionally use a body
  • access your domain model including all the GORM methods
  • call service classes
  • call other tags
    • other tags are called as methods from within a tag
  • access the session, request, and response

Implicit Objects in a GSP Tag

  • session (GrailsHttpSession)
  • request (HttpRequest)
  • response (GrailsContentBufferngResponse)
  • out (GSPResponseWriter)

Example Tag -- Output Groovy Group List

  • def groupLinks = {
    def groups = GroovyGroup.list()
    out << "<br/><ul>"
    groups.each { group ->
    out << "<li><a href='"
    out << createLink(action:'show', id:group.id)
    out << "'>${group}"
    out << "</a></li>"
    }
    out << "</ul>
    }
  • call tag as <g:groupLinks />

Example Tag With a Body

  • def ifLoggedIn = { attrs, body ->
    def user = session.use
    if (user)
    out << body()
    }
  • <g:ifLoggedIn>
    info for logged in users goes here
    </g:ifLoggedIn>
  • note that in the tag code, the first parameter will be treated as a map of attrs so even if you're not using attrs, but you're using the body, still need to have a dummy parameter for attrs in order for things to work correctly

Using Custom Tags Instead of <g:if>

  • def buttonBar = {
    def user = session.user
    out << "<div class='buttons'>"
    if (user) {
    // output buttons for logged in users here
    if (user.isAdmin()) {
    // output admin buttons here
    }
    } else {
    out << "<input type='button' value='Login' />"
    }
    out << "</div>"
    }
  • call tag as <g:buttonBar />

Testing Tags

  • very easy
  • tags when called as methods return a string
  • TagLibUnitTestCase makes it even easier
    • includes mocks for
      • session
      • request
      • response
  • includes other mocks from GrailsUnitTestCase

A Sample Test Class

  • import grails.test.*
    class DemoTagLibTests extends TagLibUnitTestCase {

    public void setUp() {
    super.setUp()
    tagLib.metaClass.createLink = { params ->
    "/groovyGroups/show/${params.id}"
    } // Had to add this because otherwise createLink wouldn't be available in the test
    // This would also be true of custom tags that aren't in the same tag library
    }

    void testButtonBarWithAdmin() {
    mockSession.user = [isAdmin:{-> true}] // map is serving as mock object
    def output = tagLib.buttonBar() // tag itself is a closure, so can call as a method here
    assert output.toString().contains('Create Stuff')
    }

    void testIfLoggedIn() {
    mockSession.user = "Anything can go here" // doesn't matter what goes here since we're just checking session.user
    def output = tagLib.ifLoggedIn([:]) {
    'User is logged in'
    }
    assertEquals output.toString(), 'User is logged in'
    }

    void testGroupLinks() {
    mockDomain(GroovyGroup, // new GroovyGroup objects here ...)
    def output = tagLib.groupLinks()
    assert output = tagLib.groupLinks()
    assert output.toString().contains('<a href=')
    assert output.toString().contains('Group2')
    }
    }

  • remember that unit tests run much faster but don't have everything available, so if you find yourself mocking a ton of stuff in unit tests, that's a good indication you need integration tests

Namespaces

  • if you don't declare a namespace, uses g
  • put static namespace = 'demo' at the top of your taglib class to declare a namespace
  • use the namespace as a prefix when calling the tag as a method, e.g. namespace.tag()

Distributing TagLibs With Plugins

  • create a plugin project
  • create or copy a TagLib to the /taglib directory
  • package your plugin
  • Install your plugin in another application
  • your custom tags are now available in the application

Demo

  • showing demo of simple custom tag to replace a lot of the redundant stuff in the grails scaffolding
  • one limitation of gsp tags is you can't have nested tags
    • you can fake this out a bit by leveraging the request scope
  • great use of gsp tags is to bundle up html, css, and javascript that gets output to the page
  • FieldData plugin available that does a lot of these sorts of things
  • check out the custom tags available in the grails core for good examples of how to do things

Comments

Popular posts from this blog

Running a Django Application on Windows Server 2012 with IIS

This is a first for me since under normal circumstances we run all our Django applications on Linux with Nginx, but we're in the process of developing an application for another department and due to the requirements around this project, we'll be handing the code off to them to deploy. They don't have any experience with Linux or web servers other than IIS, so I recently took up the challenge of figuring out how to run Django applications on Windows Server 2012 with IIS.

Based on the dated or complete lack of information around this I'm assuming it's not something that's very common in the wild, so I thought I'd share what I came up with in case others need to do this.


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Assumptions and CaveatsThe operating system is Windows Server 2012 R2, 64-bit. If another variant of the operating system is being used, these instructions may not work properly.All of the soft…

Installing and Configuring NextPVR as a Replacement for Windows Media Center

If you follow me on Google+ you'll know I had a recent rant about Windows Media Center, which after running fine for about a year suddenly decided as of January 29 it was done downloading the program guide and by extension was therefore done recording any TV shows.

I'll spare you more ranting and simply say that none of the suggestions I got (which I appreciate!) worked, and rather than spending more time figuring out why, I decided to try something different.

NextPVR is an awesome free (as in beer, not as in freedom unfortunately ...) PVR application for Windows that with a little bit of tweaking handily replaced Windows Media Center. It can even download guide data, which is apparently something WMC no longer feels like doing.

Background I wound up going down this road in a rather circuitous way. My initial goal for the weekend project was to get Raspbmc running on one of my Raspberry Pis. The latest version of XBMC has PVR functionality so I was anxious to try that out as a …

Fixing DPI Scaling Issues in Skype for Business on Windows 10

My setup for my day job these days is a Surface Pro 4 and either an LG 34UC87M-B or a Dell P2715Q monitor, depending on where I'm working. This is a fantastic setup, but some applications have trouble dealing with the high pixel density and don't scale appropriately.
One case in point is Skype for Business. For some reason it scales correctly as I move between the Surface screen and the external monitor when I use the Dell, but on the LG monitor Skype is either massive on the external monitor, or tiny on the Surface screen.
After a big of digging around I came across a solution that worked for me, which is to change a setting in Skype's manifest file (who knew there was one?). On my machine the file is here: C:\Program Files\Microsoft Office\Office16\LYNC.EXE.MANIFEST
And the setting in question is this:
<dpiAware>True/PM</dpiAware>
Which I changed to this: <dpiAware>False/PM</dpiAware>
Note that you'll probably have to edit the file as administr…