package dbnp.studycapturing import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib import dbnp.studycapturing.* import dbnp.data.* /** * Wizard tag library * * @author Jeroen Wesbeek * @since 20100113 * @package wizard * * Revision information: * $Rev: 349 $ * $Author: duh $ * $Date: 2010-04-19 11:05:48 +0000 (ma, 19 apr 2010) $ */ class WizardTagLib extends JavascriptTagLib { // define the tag namespace (e.g.: static namespace = "wizard" // define the AJAX provider to use static ajaxProvider = "jquery" // define default text field width static defaultTextFieldSize = 25; /** * ajaxButton tag, this is a modified version of the default * grails submitToRemote tag to work with grails webflows. * Usage is identical to submitToRemote with the only exception * that a 'name' form element attribute is required. E.g. * // define AJAX provider setProvider([library: ajaxProvider]) // got a function name? def functionName = attrs.remove('functionName') if (functionName && !attrs.get('name')) { attrs.name = functionName } // generate an ajax button def button = this.ajaxButton(attrs, body) // strip the button part to only leave the Ajax call button = button.replaceFirst(/<[^\"]*\"jQuery.ajax/, 'jQuery.ajax') button = button.replaceFirst(/return false.*/, '') // change form if a form attribute is present if (attrs.get('form')) { button = button.replaceFirst(/this\.form/, "\\\$('" + attrs.get('form') + "')" ) } out << button } /** * generate ajax webflow redirect javascript * * As we have an Ajaxified webflow, the initial wizard page * cannot contain a wizard form, as upon a failing submit * (e.g. the form data does not validate) the form should be * shown again. However, the Grails webflow then renders the * complete initial wizard page into the success div. As this * ruins the page layout (a page within a page) we want the * initial page to redirect to the first wizard form to enter * the webflow correctly. We do this by emulating an ajax post * call which updates the wizard content with the first wizard * form. * * Usage: * form = the form identifier * name = the action to execute in the webflow * update = the divs to update upon success or error * * OR: to generate a JavaScript function you can call yourself, use 'functionName' instead of 'name' * * Example initial webflow action to work with this javascript: * ... * mainPage {* render(view: "/wizard/index") * onRender {* flow.page = 1 *}* on("next").to "pageOne" *}* ... * * @param Map attributes * @param Closure body */ def ajaxFlowRedirect = {attrs, body -> // generate javascript out << '' } /** * render the content of a particular wizard page * @param Map attrs * @param Closure body (help text) */ def pageContent = {attrs, body -> // define AJAX provider setProvider([library: ajaxProvider]) // render new body content out << render(template: "/wizard/common/tabs") out << '
' out << body() out << '
' out << render(template: "/wizard/common/navigation") out << render(template: "/wizard/common/error") } /** * generate a base form element * @param String inputElement name * @param Map attributes * @param Closure help content */ def baseElement = {inputElement, attrs, help -> // work variables def description = attrs.remove('description') def addExampleElement = attrs.remove('addExampleElement') def addExample2Element = attrs.remove('addExample2Element') def helpText = help().trim() // got an ajax onchange action? def ajaxOnChange = attrs.remove('ajaxOnChange') if (ajaxOnChange) { if (!attrs.onChange) attrs.onChange = '' // add onChange AjaxSubmit javascript attrs.onChange += ajaxSubmitJs( [ functionName: ajaxOnChange, url: attrs.get('url'), update: attrs.get('update'), afterSuccess: attrs.get('afterSuccess') ], '' ) } // execute inputElement call def renderedElement = "$inputElement"(attrs) // if false, then we skip this element if (!renderedElement) return false // render a form element out << '
' out << '
' out << description out << '
' out << '
' out << renderedElement if (helpText.size() > 0) { out << '
' } // add an disabled input box for feedback purposes // @see dateElement(...) if (addExampleElement) { def exampleAttrs = new LinkedHashMap() exampleAttrs.name = attrs.get('name') + 'Example' exampleAttrs.class = 'isExample' exampleAttrs.disabled = 'disabled' exampleAttrs.size = 30 out << textField(exampleAttrs) } // add an disabled input box for feedback purposes // @see dateElement(...) if (addExample2Element) { def exampleAttrs = new LinkedHashMap() exampleAttrs.name = attrs.get('name') + 'Example2' exampleAttrs.class = 'isExample' exampleAttrs.disabled = 'disabled' exampleAttrs.size = 30 out << textField(exampleAttrs) } out << '
' // add help content if it is available if (helpText.size() > 0) { out << '
' out << ' ' + helpText out << '
' } out << '
' } /** * render an ajaxButtonElement * @param Map attrs * @param Closure body (help text) */ def ajaxButtonElement = { attrs, body -> baseElement.call( 'ajaxButton', attrs, body ) } /** * render a textFieldElement * @param Map attrs * @param Closure body (help text) */ def textFieldElement = {attrs, body -> // set default size, or scale to max length if it is less than the default size if (!attrs.get("size")) { if (attrs.get("maxlength")) { attrs.size = ((attrs.get("maxlength") as int) > defaultTextFieldSize) ? defaultTextFieldSize : attrs.get("maxlength") } else { attrs.size = defaultTextFieldSize } } // render template element baseElement.call( 'textField', attrs, body ) } /** * render a select form element * @param Map attrs * @param Closure body (help text) */ def selectElement = {attrs, body -> baseElement.call( 'select', attrs, body ) } /** * render a checkBox form element * @param Map attrs * @param Closure body (help text) */ def checkBoxElement = {attrs, body -> baseElement.call( 'checkBox', attrs, body ) } /** * render a dateElement * NOTE: datepicker is attached through wizard.js! * @param Map attrs * @param Closure body (help text) */ def dateElement = {attrs, body -> // transform value? if (attrs.value instanceof Date) { // transform date instance to formatted string (dd/mm/yyyy) attrs.value = String.format('%td/% // transform value? if (attrs.value instanceof Date) { // transform date instance to formatted string (dd/mm/yyyy) attrs.value = String.format('%td/% // render template element baseElement.call( 'speciesSelect', attrs, body ) } /** * Button form element * @param Map attributes * @param Closure help content */ def buttonElement = {attrs, body -> // render template element baseElement.call( 'ajaxButton', attrs, body ) } /** * render a species select element * @param Map attrs */ def speciesSelect = {attrs -> // fetch the speciesOntology // note that this is a bit nasty, probably the ontologyName should // be configured in a configuration file... --> TODO: centralize species configuration def speciesOntology = Ontology.findByName('NCBI Taxonomy') // fetch all species attrs.from = Term.findAllByOntology(speciesOntology) // got a name? if (!attrs.name) { // nope, use a default name attrs.name = 'species' } out << select(attrs) } /** * Ontology form element * @param Map attributes * @param Closure help content */ def ontologyElement = { attrs, body -> // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form // @see ontology-chooser.js, table-editor.js baseElement.call( 'textField', [ name: attrs.name, value: attrs.value, description: attrs.description, rel: 'ontology-' + ((attrs.ontology) ? attrs.ontology : 'all') + '-name', size: 25 ], body ) out << hiddenField( name: attrs.name + '-concept_id' ) out << hiddenField( name: attrs.name + '-ontology_id' ) out << hiddenField( name: attrs.name + '-full_id' ) } /** * Study form element * @param Map attributes * @param Closure help content */ def studyElement = { attrs, body -> // render study element baseElement.call( 'studySelect', attrs, body ) } /** * render a study select element * @param Map attrs */ def studySelect = { attrs -> // for now, just fetch all studies attrs.from = Study.findAll() // got a name? if (!attrs.name) { attrs.name = "study" } // got result? if (attrs.from.size() > 0) { out << select(attrs) } else { // no, return false to make sure this element // is not rendered in the template return false } } /** * Template form element * @param Map attributes * @param Closure help content */ def templateElement = {attrs, body -> // render template element baseElement.call( 'templateSelect', attrs, body ) } /** * render a template select element * @param Map attrs */ def templateSelect = {attrs -> def entity = attrs.remove('entity') // fetch templates if (attrs.remove('addDummy')) { attrs.from = [''] if (entity && entity instanceof Class) { Template.findAllByEntity(entity).each() { attrs.from[attrs.from.size()] = it } } } else { attrs.from = (entity) ? Template.findAllByEntity(entity) : Template.findAll() } // got a name? if (!attrs.name) { attrs.name = 'template' } // got result? if (attrs.from.size() > 0) { out << select(attrs) } else { // no, return false to make sure this element // is not rendered in the template return false } } /** * Term form element * @param Map attributes * @param Closure help content */ def termElement = {attrs, body -> // render term element baseElement.call( 'termSelect', attrs, body ) } /** * render a term select element * @param Map attrs */ def termSelect = {attrs -> // fetch all terms attrs.from = Term.findAll() // for now, all terms as we cannot identify terms as being treatment terms... // got a name? if (!attrs.name) { attrs.name = 'term' } out << select(attrs) } /** * Protocol form element * @param Map attributes * @param Closure help content */ def protocolElement = {attrs, body -> // render protocol element baseElement.call( 'protocolSelect', attrs, body ) } /** * render a protocol select element * @param Map attrs */ def protocolSelect = {attrs -> // fetch all protocold attrs.from = Protocol.findAll() // for now, all protocols // got a name? if (!attrs.name) { attrs.name = 'protocol' } out << select(attrs) } def show = {attrs -> // is object parameter set? def o = attrs.object println o.getProperties(); o.getProperties().each { println it } out << "!! test version of 'show' tag !!" } /** * render table headers for all subjectFields in a template * @param Map attributes */ def templateColumnHeaders = {attrs -> def template = attrs.remove('template') // output table headers for template fields template.fields.each() { out << '
' + it + '
' } } /** * render table input elements for all subjectFields in a template * @param Map attributes */ def templateColumns = {attrs, body -> def subject = attrs.remove('subject') def subjectId = attrs.remove('id') def template = attrs.remove('template') def intFields = subject.templateIntegerFields def stringFields = subject.templateStringFields def floatFields = subject.templateFloatFields def termFields = subject.templateTermFields // output columns for these subjectFields template.fields.each() { def fieldValue = subject.getFieldValue(it.name) // output div out << '
' // handle field types switch (it.type.toString()) { case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']: out << textField( name: attrs.name + '_' + it.escapedName(), value: fieldValue ) break case 'STRINGLIST': // render stringlist subjectfield if (!it.listEntries.isEmpty()) { out << select( name: attrs.name + '_' + it.escapedName(), from: it.listEntries, value: fieldValue ) } else { out << 'no values!!' } break case 'DATE': // transform value? if (fieldValue instanceof Date) { if (fieldValue.getHours() == 0 && fieldValue.getMinutes() == 0) { // transform date instance to formatted string (dd/mm/yyyy) fieldValue = String.format('%td/%!' + it.type + '' break } out << '
' } } /** * render form elements based on an entity's template * @param Map attributes * @param String body */ def templateElements = {attrs -> def entity = (attrs.get('entity')) def template = (entity && entity instanceof TemplateEntity) ? entity.template : null // got a template? if (template) { // render template fields template.fields.each() { def fieldValue = entity.getFieldValue(it.name) switch (it.type.toString()) { case ['STRING', 'TEXT', 'INTEGER', 'FLOAT', 'DOUBLE']: out << textFieldElement( description: it.name, name: it.escapedName(), value: fieldValue ) break case 'STRINGLIST': if (!it.listEntries.isEmpty()) { out << selectElement( description: it.name, name: it.escapedName(), from: it.listEntries, value: fieldValue ) } else { out << 'no values!!' } break case 'ONTOLOGYTERM': // @see http://www.bioontology.org/wiki/index.php/NCBO_Widgets#Term-selection_field_on_a_form // @see ontology-chooser.js out << textFieldElement( name: it.escapedName(), value: fieldValue, rel: 'ontology-all-name', size: 100 ) out << hiddenField( name: it.name + '-concept_id', value: fieldValue ) out << hiddenField( name: it.escapedName() + '-ontology_id',