tags:

views:

924

answers:

3

I've been using tags in my projects. I was browsing the custom tags on grails.org to find some new tags for my library.

http://www.grails.org/Contribute+a+Tag

I was wondering if people in the StackOverflow community have a favorite custom tag that they would like to share.

+1  A: 

I have a "fmt:relDate" tag that gives you Twitter-like relative dates "3 days ago", "less than 30 seconds ago", etc., with the real time as a tooltip.

The current implementation is basically a gigantic chain of if/then statements with the boundaries that I like. A binary-search based algorithm would be better (in the sense of "more efficient"), and the current implementation has my personal preferences encoded into it, so I'm reluctant to share the tag.

Robert Fischer
A: 

I have a remote paginate tab, that helps me paginate results via ajax. Its an improvement over the default tab and takes in custom arguments.

Here's the code:

class CustomRemotePaginateTagLib {

  static namespace = 'myTagLib'

  /** * Creates next/previous links to support pagination for the current controller * * <g:paginate total="$ { Account.count() } " />               */
  def remotePaginate = {attrs ->
    def writer = out
    if (attrs.total == null) throwTagError("Tag [remotePaginate] is missing required attribute [total]")

    if (attrs.update == null) throwTagError("Tag [remotePaginate] is missing required attribute [update]")

    def locale = RequestContextUtils.getLocale(request)

    def total = attrs.total.toInteger()

    def update = attrs.update

    def action = (attrs.action ? attrs.action : (params.action ? params.action : "list"))
    def controller = (attrs.controller ? attrs.controller : params.controller)
    def offset = params.offset?.toInteger()
    def max = params.max?.toInteger()
    def maxsteps = (attrs.maxsteps ? attrs.maxsteps.toInteger() : 10)

    if (!offset) offset = (attrs.offset ? attrs.offset.toInteger() : 0)
    if (!max) max = (attrs.max ? attrs.max.toInteger() : 10)

    def linkParams = [offset: offset - max, max: max]
    if (params.sort) linkParams.sort = params.sort
    if (params.order) linkParams.order = params.order
    if (attrs.params) linkParams.putAll(attrs.params)
    linkParams['action'] = action
    linkParams['controller'] = controller

    def linkTagAttrs = [url: "#"]
    if (attrs.controller) { linkTagAttrs.controller = attrs.controller }
    if (attrs.id != null) { linkTagAttrs.id = attrs.id }

    // determine paging variables
    def steps = maxsteps > 0
    int currentstep = (offset / max) + 1
    int firststep = 1
    int laststep = Math.round(Math.ceil(total / max))

    // display previous link when not on firststep
    if (currentstep > firststep) {
      linkTagAttrs.class = 'prevLink'
      def prevOffset = linkParams.offset

      def params = attrs.params ?: []
      params.'offset' = prevOffset

      linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
      writer << link(linkTagAttrs.clone()) {
        (attrs.prev ? attrs.prev : g.message(code: 'default.paginate.prev', default: 'Previous'))
      }
    }

    // display steps when steps are enabled and laststep is not firststep
    if (steps && laststep > firststep) {
      linkTagAttrs.class = 'step'

      // determine begin and endstep paging variables
      int beginstep = currentstep - Math.round(maxsteps / 2) + (maxsteps % 2)
      int endstep = currentstep + Math.round(maxsteps / 2) - 1

      if (beginstep < firststep) {
        beginstep = firststep
        endstep = maxsteps
      }
      if (endstep > laststep) {
        beginstep = laststep - maxsteps + 1
        if (beginstep < firststep) {
          beginstep = firststep
        }
        endstep = laststep
      }

      // display firststep link when beginstep is not firststep
      if (beginstep > firststep) {
        linkParams.offset = 0

        def params = attrs.params ?: []
        params['offset'] = linkParams.offset

        linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
        writer << link(linkTagAttrs.clone()) { firststep.toString() }
        writer << '<span class="step">..</span>'
      }

      // display paginate steps
      (beginstep..endstep).each {i ->
        if (currentstep == i) {
          writer << "<span class=\"currentStep\">${i}</span>"
        } else {
          linkParams.offset = (i - 1) * max

          def params = attrs.params ?: []
          params['offset'] = linkParams.offset

          linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
          writer << link(linkTagAttrs.clone()) { i.toString() }
        }
      }

      // display laststep link when endstep is not laststep
      if (endstep < laststep) {
        writer << '<span class="step">..</span>'
        linkParams.offset = (laststep - 1) * max

        def params = attrs.params ?: []
        params['offset'] = linkParams.offset

        linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
        writer << link(linkTagAttrs.clone()) { laststep.toString() }
      }
    }

    // display next link when not on laststep
    if (currentstep < laststep) {
      linkTagAttrs.class = 'nextLink'
      linkParams.offset = offset + max

      def params = attrs.params ?: []
      params['offset'] = linkParams.offset

      linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
      writer << link(linkTagAttrs.clone()) {
        (attrs.next ? attrs.next : g.message(code: 'default.paginate.next', default: 'Next'))
      }
    }

  }
ankimal
+1  A: 

I find the DecimalFormat class (and Grails's formatNumber tag by extension) a bit opaque for certain use cases, and I still haven't found a reasonable way to do some pretty basic formatting with it without some ugly pre-processing to generate an appropriate format string. I threw together a simple number formatting tag several months ago which essentially constructs a format string and does some minimal processing to the number itself.

It's not as generic or elegant as I'd like (it's all we needed at the time - it's super basic, but it still keeps some ugly processing out of GSPs), but it should be easy to read, and it's obvious where it could be trivially improved (i.e. making the scaling iterative instead of naive if-elseif slop, allowing the user to pass in custom scaling markers, allowing for a custom number validator as a parameter, etc.).


// Formats a number to 3 significant digits, appending appropriate scale marker
// (k, m, b, t, etc.). Defining var allows you to use a string representation
// of the formatted number anywhere you need it within the tag body, and 
// provides the scale as well (in case highlighting or other special formatting
// based upon scale is desired).
def formatNumberScaled = {attrs, body -> // number, prefix, suffix, invalid, var
    Double number
    String numberString
    String scale

    try {
        number = attrs.'number'.toDouble()
    } catch (Exception e) {
        number = Double.NaN
    }

    if (number.isNaN() || number.isInfinite()) {
        numberString = scale = attrs.'invalid' ?: "N/A"
    } else {
        Boolean negative = number < 0d
        number = negative ? -number : number

        if (number < 1000d) {
            scale = ''
        } else if (number < 1000000d) {
            scale = 'k'
            number /= 1000d
        } else if (number < 1000000000d) {
            scale = 'm'
            number /= 1000000d
        } else if (number < 1000000000000d) {
            scale = 'b'
            number /= 1000000000d
        } else if (number < 1000000000000000d) {
            scale = 't'
            number /= 1000000000000d
        }

        String format
        if (number < 10d) {
            format = '#.00'
        } else if (number < 100d) {
            format = '##.0'
        } else {
            format = '###'
        }
        format = "'${attrs.'prefix' ?: ''}'${format}'${scale} ${attrs.'suffix' ?: ''}'"

        numberString = g.formatNumber('number': negative ? -number : number, 'format': format)
    }

    // Now, either print the number or output the tag body with
    // the appropriate variables set
    if (attrs.'var') {
        out << body((attrs.'var'): numberString, 'scale': scale)
    } else {
        out << numberString
    }
}
Chris King
@ankimal - don't have enough rep to comment on others - have you tried:<br/>http://www.grails.org/Contribute+a+Tag#remotePaginate<br/><br/>Does that have a deficiency of some type, or is your custom tag just specially suited to some task?
Chris King