Changeset 1591


Ignore:
Timestamp:
Mar 7, 2011, 12:01:52 PM (9 years ago)
Author:
robert@…
Message:

Improved simple wizard so that is can edit studies

Location:
trunk/grails-app
Files:
2 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/dbnp/studycapturing/SimpleWizardController.groovy

    r1581 r1591  
    6868                                // If the user clicks next, the study should be validated
    6969                                if( event == "next" && validateObject( study ) ) {
    70                                         toPage( "samples" );
     70                                        if( study.samples?.size() ) {
     71                                                // This wizard can only handle simple studies. If the
     72                                                // study is too complex, this wizard doesn't provide the
     73                                                // possibility to edit samples/subjects etc.
     74                                                if( checkStudySimplicity( study ) ) {
     75                                                        toPage( "existingSamples" );
     76                                                } else {
     77                                                        toPage( "complexStudy" );
     78                                                }
     79                                        } else {
     80                                                toPage( "samples" );
     81                                        }
     82                                       
    7183                                        return;
    7284                                }
     
    7688                // Give the study to the user
    7789                [ study: study ]
     90        }
     91       
     92        /**
     93         * Shows a page to mention that the study being edited is too complex
     94         */
     95        def complexStudy = {
     96                // Retrieve the correct study
     97                study = getStudyInWizard( params );
     98                if( !study ) {
     99                        redirect( controller: 'simpleWizard', action: 'study' );
     100                        return;
     101                }
     102
     103                def event = getEvent(params);
     104
     105                // If any event on this page is triggered
     106                if( event ) {
     107                        // Now determine what action to perform
     108                        if( event == "save" ) {
     109                                toPage( "save" );
     110                                return;
     111                        } else if( event == "previous" ) {
     112                                toPage( "study" );
     113                                return;
     114                        }
     115                }
     116
     117                // Give the study and other data to the user
     118                [ study: study ]
     119        }
     120
     121        /**
     122         * Shows the samples page
     123         */
     124        def existingSamples = {
     125                // Retrieve the correct study
     126                study = getStudyInWizard( params );
     127                if( !study ) {
     128                        redirect( controller: 'simpleWizard', action: 'study' );
     129                        return;
     130                }
     131
     132                def event = getEvent(params);
     133
     134                // If any event on this page is triggered, we should save the entered data.
     135                // If no event is triggered, the user gets here from another page. In that case,
     136                // we don't set the values
     137                if( event ) {
     138                        // Now determine what action to perform
     139                        if( event == "next" && handleExistingSamples( study, params )) {
     140                                // Only continue to the next page if the information entered is correct
     141                                toPage( "assays" );
     142                                return;
     143                        } else if( event == "previous" ) {
     144                                // The user may go to the previous page, even if none of the data entered is OK.
     145                                toPage( "study" );
     146                                return;
     147                        } else if( event == "update" && handleExistingSamples( study, params ) ) {
     148                                // The user may update the samples using excel. Before that, the sample date should be saved
     149                                toPage( "samples" );
     150                                return;
     151                        } else if( event == "skip" ) {
     152                                // The user may skip the complete samples page
     153                                toPage( "assays" );
     154                                return;
     155                        }
     156                }
     157
     158                // Give the study and other data to the user
     159                def records = importerService.getRecords( study );
     160
     161                [ study: study,
     162                        records: records,
     163                        templateCombinations: records.templateCombination.unique(),
     164                        templates: [
     165                                'Sample': Template.findAllByEntity( Sample.class ),
     166                                'Subject': Template.findAllByEntity( Subject.class ),
     167                                'Event': Template.findAllByEntity( Event.class ),
     168                                'SamplingEvent': Template.findAllByEntity( SamplingEvent.class )
     169                        ],
     170                        encodedEntity: [
     171                                'Sample': gdtService.encryptEntity( Sample.class.name ),
     172                                'Subject': gdtService.encryptEntity( Subject.class.name ),
     173                                'Event': gdtService.encryptEntity( Event.class.name ),
     174                                'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name )
     175                        ],
     176                        existingSampleForm: session.simpleWizard.existingSampleForm
     177                ]
    78178        }
    79179
     
    102202                        } else if( event == "previous" ) {
    103203                                // The user may go to the previous page, even if none of the data entered is OK.
    104                                 toPage( "study" );
     204                                if( study.samples?.size() )
     205                                        toPage( "existingSamples" );
     206                                else
     207                                        toPage( "study" );
     208
    105209                                return;
    106210                        } else if( event == "skip" ) {
     
    112216
    113217                // Give the study and other data to the user
    114                 [ study: study, sampleTemplates: Template.findAllByEntity( Sample.class ), encodedEntity: gdtService.encryptEntity( Sample.class.name ), sampleForm: session.simpleWizard.sampleForm ]
     218                [ study: study,
     219                        templates: [
     220                                'Sample': Template.findAllByEntity( Sample.class ),
     221                                'Subject': Template.findAllByEntity( Subject.class ),
     222                                'Event': Template.findAllByEntity( Event.class ),
     223                                'SamplingEvent': Template.findAllByEntity( SamplingEvent.class )
     224                        ],
     225                        encodedEntity: [
     226                                'Sample': gdtService.encryptEntity( Sample.class.name ),
     227                                'Subject': gdtService.encryptEntity( Subject.class.name ),
     228                                'Event': gdtService.encryptEntity( Event.class.name ),
     229                                'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name )
     230                        ],
     231                        sampleForm: session.simpleWizard.sampleForm ]
    115232        }
    116233
     
    134251                        // Now determine what action to perform
    135252                        if( event == "next" && handleColumns( study, params ) ) {
     253                               
    136254                                // Only continue to the next page if the information entered is correct
    137255                                if( session.simpleWizard.imported.numInvalidEntities > 0 ) {
     
    142260                                                fileService.delete( session.simpleWizard.sampleForm.importFile );
    143261
     262                                        session.simpleWizard.sampleForm = null
     263
    144264                                        toPage( "assays" );
    145265                                }
    146266                                return;
    147267                        } else if( event == "previous" ) {
    148                                 // THe user may go to the previous page, even if the data is not correct
     268                                // The user may go to the previous page, even if the data is not correct
    149269                                toPage( "samples" );
    150270                                return;
    151271                        }
    152272                }
    153 
     273               
     274                def templates = [:];
     275                def domainFields = [:]
     276
     277                session.simpleWizard.sampleForm.templateId.each {
     278                        templates[ it.key ] = it.value ? Template.get( it.value ) : null;
     279                        if( it.value ) {
     280                                domainFields[ it.key ] = templates[ it.key ].entity.giveDomainFields();
     281                        }
     282                }
     283               
    154284                // Give the study and other data to the user
    155285                [ study: study,
    156286                                        filename: session.simpleWizard.sampleForm.importFile,
    157                                         template: Template.get( session.simpleWizard.sampleForm.templateId ),
     287                                        templates: templates,
     288                                        domainFields: domainFields,
    158289                                        excel: session.simpleWizard.excel]
    159290        }
     
    180311                                if( session.simpleWizard.imported.numInvalidEntities == 0 ) {
    181312                                        // Only continue to the next page if the information entered is correct
    182                                        
     313
    183314                                        // The import of the excel file has finished. Now delete the excelfile
    184                                         if( session.simpleWizard.sampleForm.importFile )
     315                                        if( session.simpleWizard.sampleForm.importFile ) {
    185316                                                fileService.delete( session.simpleWizard.sampleForm.importFile );
    186                                                
     317                                        }
     318                                        session.simpleWizard.sampleForm = null
     319
    187320                                        toPage( "assays" );
    188321                                        return;
     
    203336                }
    204337
     338                // Create the right format for showing the data
     339                def records = [];
     340                session.simpleWizard.imported.data.each { row ->
     341                        def record = [:];
     342                        row.each { object ->
     343                                // Attach template to session
     344                                if( object.template )
     345                                        attachTemplate( object.template );
     346                               
     347                                def entityName = object.class.name[ object.class.name.lastIndexOf( '.' ) + 1 .. -1 ]
     348                                record[ entityName ] = object;
     349                        }
     350                        records << record;
     351                }
     352               
    205353                // Give the study and other data to the user
    206                 [ study: study, imported: session.simpleWizard.imported, rules: rules ]
     354                println "Imported: " + session.simpleWizard.imported.failedCells;
     355               
     356                [ study: study, imported: session.simpleWizard.imported, records: records, rules: rules ]
    207357        }
    208358
     
    217367                        return;
    218368                }
    219                
    220                 Assay assay
    221                 if( study.assays?.size() ) {
    222                         assay = study.assays[0];
    223                         study.removeFromAssays( assay );
    224                 } else {
    225                         assay = new Assay();
    226                 }
    227                        
     369
     370                Assay assay = getAssayInWizard( study );
     371
    228372                def event = getEvent(params);
    229373
     
    235379                        if( event == "skip" ) {
    236380                                // The user may skip the complete assay page
     381
     382                                // In case the user has created an assay before, it should only be kept if it
     383                                // existed before this step
     384                                if( session.simpleWizard.assay != null && !session.simpleWizard.assay.id ) {
     385                                        session.simpleWizard.remove( "assay" )
     386                                }
     387
    237388                                toPage( "overview" );
    238389                                return;
    239390                        } else if( handleAssays( assay, params ) ) {
    240                                 study.addToAssays( assay );
    241                                
    242391                                // Now determine what action to perform
    243                                 if( event == "next" && validateObject( study ) ) {
     392                                if( event == "next" && validateObject( assay ) ) {
    244393                                        toPage( "overview" );
    245394                                        return;
    246395                                } else if( event == "previous" ) {
    247                                         toPage( "samples" )
     396                                        if( study.samples?.size() )
     397                                                toPage( "existingSamples" )
     398                                        else
     399                                                toPage( "samples" );
     400
    248401                                        return;
    249402                                }
     
    252405
    253406                // Give the study to the user
    254                 [ study: study, assay: assay ]
    255         }
    256        
     407                [ study: study, wizardAssay: assay ]
     408        }
     409
    257410        /**
    258411         * Shows an overview of the entered study
     
    265418                        return;
    266419                }
    267                        
     420
     421                Assay assay = getAssayInWizard();
     422
    268423                def event = getEvent(params);
    269424
     
    277432                                return;
    278433                        } else if( event == "previous" ) {
    279                                 toPage( "assay" )
     434                                toPage( "assays" )
    280435                                return;
    281436                        }
     
    283438
    284439                // Give the study to the user
    285                 [ study: study ]
    286         }
    287        
     440                [ study: study, wizardAssay: assay ]
     441        }
     442
    288443        def save = {
    289444                // Retrieve the correct study
     
    293448                        return;
    294449                }
    295                
    296                 // Make sure all samples are attached to all assays
    297                 study.assays.each { assay ->
    298                         assay.samples?.clear();
    299                         study.samples.each { sample ->
    300                                 assay.addToSamples( sample )
    301                         }
    302                 }
    303                
    304                 // Save the study
    305                 if( study.save( flush: true ) ) {
     450
     451                def event = getEvent(params);
     452
     453                if( event && event == "previous" ) {
     454                        toPage( "assays" );
     455                        return;
     456                }
     457               
     458                Assay newAssay = getAssayInWizard();
     459
     460                if( newAssay && !study.assays?.contains( newAssay ) ) {
     461                        study.addToAssays( newAssay );
     462                }
     463
     464                //attachAndValidateEntities( study );
     465               
     466                // Save the study. The study must be merged if it is loaded from
     467                // the database before, since otherwise it will raise 'lazy initialization' errors
     468                // The validation is done in the other steps, so it is skipped here.
     469                def success
     470               
     471                if(
     472                study.id ?
     473                study.merge( validate: false, flush: true ) :
     474                study.save( flush: true )
     475                ) {
     476                        // Make sure all samples are attached to all assays
     477                        study.assays.each { assay ->
     478                                def l = []+ assay.samples;
     479                                l.each { sample ->
     480                                        if( sample )
     481                                                assay.removeFromSamples( sample );
     482                                }
     483                                assay.samples?.clear();
     484
     485                                study.samples.each { sample ->
     486                                        assay.addToSamples( sample )
     487                                }
     488                        }
     489
    306490                        // Clear session
    307491                        session.simpleWizard = null;
    308                        
     492
    309493                        flash.message = "Your study is succesfully saved.";
     494                        success = true
    310495                } else {
    311496                        flash.error = "An error occurred while saving your study";
     497
     498                        study.getErrors().each { println it }
     499
     500                        // Remove the assay from the study again, since it is still available
     501                        // in the session
     502                        if( newAssay ) {
     503                                study.removeFromAssays( newAssay );
     504                                newAssay.parent = study;
     505                        }
     506
    312507                        //validateObject( study );
     508                        success = false
    313509                }
    314510
    315511                // Give the study to the user
    316                 [ study: study ]
     512                [ study: study, success: success ]
    317513        }
    318514
     
    353549                handleStudyUsers(study, params, 'readers')
    354550                handleStudyUsers(study, params, 'writers')
    355                
     551
    356552                return true
     553        }
     554
     555        /**
     556         * Handles the editing of existing samples
     557         * @param study         Study to update
     558         * @param params                Request parameter map
     559         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
     560         */
     561        def handleExistingSamples( study, params ) {
     562                session.simpleWizard.existingSampleForm = params
     563                flash.validationErrors = [:];
     564
     565                def errors = false;
     566               
     567                // iterate through objects; set field values and validate the object
     568                def eventgroups = study.samples.parentEventGroup.findAll { it }
     569                def events;
     570                if( !eventgroups )
     571                        events = []
     572                else
     573                        events = eventgroups.events?.getAt(0);
     574               
     575                def objects = [
     576                        'Sample': study.samples,
     577                        'Subject': study.samples.parentSubject.findAll { it },
     578                        'SamplingEvent': study.samples.parentEvent.findAll { it },
     579                        'Event': events.flatten().findAll { it }
     580                ];     
     581                objects.each {
     582                        def type = it.key;
     583                        def entities = it.value;
     584                       
     585                        entities.each { entity ->
     586                                // iterate through entity fields
     587                                entity.giveFields().each() { field ->
     588                                        def value = params.get( type.toLowerCase() + '_' + entity.getIdentifier() + '_' + field.escapedName())
     589
     590                                        // set field value; name cannot be set to an empty value
     591                                        if (field.name != 'name' || value) {
     592                                                log.info "setting "+field.name+" to "+value
     593                                                entity.setFieldValue(field.name, value)
     594                                        }
     595                                }
     596                               
     597                                // has the template changed?
     598                                def templateName = params.get(type.toLowerCase() + '_' + entity.getIdentifier() + '_template')
     599                                if (templateName && entity.template?.name != templateName) {
     600                                        entity.template = Template.findByName(templateName)
     601                                }
     602       
     603                                // validate sample
     604                                if (!entity.validate()) {
     605                                        errors = true;
     606                                        flash.validationErrors << getHumanReadableErrors( entity )
     607                                }
     608                        }
     609                }
     610
     611                return !errors
    357612        }
    358613
     
    370625                // still being in the temporary directory.
    371626                // This import step doesn't have to make that distinction, since all files remain in the temporary directory.
    372                 if( filename[0..8] == 'existing*' )
     627                if( filename == 'existing*' )
     628                        filename = '';
     629                else if( filename[0..8] == 'existing*' )
    373630                        filename = filename[9..-1]
    374 
    375                 def templateId  = params.long( 'template_id' )
     631               
     632                def sampleTemplateId  = params.long( 'sample_template_id' )
     633                def subjectTemplateId  = params.long( 'subject_template_id' )
     634                def eventTemplateId  = params.long( 'event_template_id' )
     635                def samplingEventTemplateId  = params.long( 'samplingEvent_template_id' )
     636               
     637                // These fields have been removed from the form, so will always contain
     638                // their default value. The code however remains like this for future use.
    376639                int sheetIndex = (params.int( 'sheetindex' ) ?: 1 )
    377640                int dataMatrixStart = (params.int( 'datamatrix_start' ) ?: 2 )
     
    381644                session.simpleWizard.sampleForm = [
    382645                                        importFile: filename,
    383                                         templateId: templateId,
     646                                        templateId: [
     647                                                'Sample': sampleTemplateId,
     648                                                'Subject': subjectTemplateId,
     649                                                'Event': eventTemplateId,
     650                                                'SampingEvent': samplingEventTemplateId
     651                                        ],
    384652                                        sheetIndex: sheetIndex,
    385653                                        dataMatrixStart: dataMatrixStart,
     
    388656
    389657                // Check whether the template exists
    390                 if (!templateId || !Template.get( templateId ) ){
    391                         log.error ".simple study wizard not all fields are filled in"
     658                if (!sampleTemplateId || !Template.get( sampleTemplateId ) ){
     659                        log.error ".simple study wizard not all fields are filled in: " + sampleTemplateId
    392660                        flash.error = "No template was chosen. Please choose a template for the samples you provided."
    393661                        return false
     
    474742        def handleColumns( study, params ) {
    475743                // Find actual Template object from the chosen template name
    476                 def template = Template.get(session.simpleWizard.sampleForm.templateId)
     744                def templates = [:];
     745                session.simpleWizard.sampleForm.templateId.each {
     746                        templates[ it.key ] = it.value ? Template.get( it.value ) : null;
     747                }
     748               
    477749                def headers = session.simpleWizard.excel.data.header;
    478750
     
    485757                // Retrieve the chosen matches from the request parameters and put them into
    486758                // the headers-structure, for later reference
    487                 params.matches.index.each { columnindex, property ->
     759                params.matches.index.each { columnindex, value ->
     760                        // Determine the entity and property by splitting it
     761                        def parts = value.toString().tokenize( "||" );
     762                       
     763                        def property
     764                        def entityName
     765                        if( parts.size() > 1 ) {
     766                                property = parts[ 1 ];
     767                                entityName = "dbnp.studycapturing." + parts[ 0 ];
     768                        } else if( parts.size() == 1 ) {
     769                                property = parts[ 0 ];
     770                                entityName = headers[columnindex.toInteger()].entityclass.getName();
     771                        }
     772                       
    488773                        // Create an actual class instance of the selected entity with the selected template
    489774                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
    490                         def entityClass = Class.forName( headers[columnindex.toInteger()].entityclass.getName(), true, this.getClass().getClassLoader())
    491                         def entityObj = entityClass.newInstance(template: template)
    492 
     775                        def entityClass = Class.forName( entityName, true, this.getClass().getClassLoader())
     776                        def entityObj = entityClass.newInstance(template: templates[ entityName[entityName.lastIndexOf( '.' ) + 1..-1] ])
     777
     778                        headers[ columnindex.toInteger() ].entityclass = entityClass
     779                       
    493780                        // Store the selected property for this column into the column map for the ImporterService
    494781                        headers[columnindex.toInteger()].property = property
     
    501788
    502789                        //if it's an identifier set the mapping column true or false
    503                         entityObj.giveFields().each {
     790                        entityClass.giveDomainFields().each {
    504791                                headers[columnindex.toInteger()].identifier = ( it.preferredIdentifier && (it.name == property) )
    505792                        }
     
    507794
    508795                // Import the workbook and store the table with entity records and store the failed cells
    509                 def (table, failedcells) = importerService.importData(session.simpleWizard.sampleForm.templateId,
     796                println "Importing samples for study " + study + " (" + study.id + ")";
     797               
     798                def imported = importerService.importOrUpdateDataBySampleIdentifier(templates,
    510799                                session.simpleWizard.excel.workbook,
    511800                                session.simpleWizard.excel.sheetIndex - 1,
    512801                                session.simpleWizard.excel.dataMatrixStart - 1,
    513                                 session.simpleWizard.excel.data.header)
     802                                session.simpleWizard.excel.data.header,
     803                                study,
     804                                true                    // Also create entities for which no data is imported but where templates were chosen
     805                );
     806
     807                def table = imported.table
     808                def failedcells = imported.failedCells
    514809
    515810                session.simpleWizard.imported = [
    516                                         data: table,
    517                                         failedCells: failedcells
    518                                 ];
     811                        data: table,
     812                        failedCells: failedcells
     813                ];
    519814
    520815                // loop through all entities to validate them and add them to failedcells if an error occurs
     
    522817                def errors = [];
    523818
    524                 // Remove all samples
    525                 study.samples?.clear();
    526 
     819                // Add all samples
    527820                table.each { record ->
    528821                        record.each { entity ->
     822                                if( entity ) {
     823                                        // Determine entity class and add a parent
     824                                        def preferredIdentifier = importerService.givePreferredIdentifier( entity.class );
     825                                        def equalClosure = { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) }
     826       
     827                                        entity.parent = study
     828                                       
     829                                        switch( entity.class ) {
     830                                                case Sample:
     831                                                        if( preferredIdentifier && !study.samples?.find( equalClosure ) ) {
     832                                                                study.addToSamples( entity );
     833                                                        }
     834                                                        break;
     835                                                case Subject:
     836                                                        if( preferredIdentifier && !study.subjects?.find( equalClosure ) ) {
     837                                                                study.addToSubjects( entity );
     838                                                        }
     839                                                        break;
     840                                                case Event:
     841                                                        if( preferredIdentifier && !study.events?.find( equalClosure ) ) {
     842                                                                study.addToEvents( entity );
     843                                                        }
     844                                                        break;
     845                                                case SamplingEvent:
     846                                                        if( preferredIdentifier && !study.samplingEvents?.find( equalClosure ) ) {
     847                                                                study.addToSamplingEvents( entity );
     848                                                        }
     849                                                        break;
     850                                        }
     851                                       
     852                                        if (!entity.validate()) {
     853                                                numInvalidEntities++;
     854                                               
     855                                                // Add this field to the list of failed cells, in order to give the user feedback
     856                                                failedcells = addNonValidatingCells( failedcells, entity )
     857       
     858                                                // Also create a full list of errors
     859                                                errors += getHumanReadableErrors( entity );
     860                                        }
     861                                }
     862                        }
     863                }
     864
     865                session.simpleWizard.imported.numInvalidEntities = numInvalidEntities + failedcells?.size();
     866                session.simpleWizard.imported.errors = errors;
     867
     868                return true
     869        }
     870
     871        /**
     872         * Handles the update of the edited fields by the user
     873         * @param study         Study to update
     874         * @param params                Request parameter map
     875         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error.
     876         *                                      The field session.simpleWizard.imported.numInvalidEntities reflects the number of
     877         *                                      entities that still have errors, and should be fixed before saving. The errors for those entities
     878         *                                      are saved into session.simpleWizard.imported.errors
     879         */
     880        def handleMissingFields( study, params ) {
     881                def numInvalidEntities = 0;
     882                def errors = [];
     883
     884                // Check which fields failed previously
     885                def failedCells = session.simpleWizard.imported.failedCells
     886
     887                session.simpleWizard.imported.data.each { table ->
     888                        table.each { entity ->
     889                                def invalidFields = 0
     890
     891                                // Set the fields for this entity by retrieving values from the params
     892                                entity.giveFields().each { field ->
     893                                        def fieldName = importerService.getFieldNameInTableEditor( entity, field );
     894
     895                                        if( params[ fieldName ] == "#invalidterm" ) {
     896                                                // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect
     897                                                invalidFields++;
     898                                        } else {
     899                                                if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) {
     900                                                        // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from
     901                                                        // the failedCells list
     902                                                        importerService.removeFailedCell( failedCells, entity, field )
     903                                                }
     904
     905                                                // Update the field, regardless of the type of field
     906                                                entity.setFieldValue(field.name, params[ fieldName ] )
     907                                        }
     908                                }
     909
    529910                                // Determine entity class and add a parent
    530                                 entity.parent = study
    531                                 study.addToSamples( entity );
    532 
    533                                 if (!entity.validate()) {
     911                                entity.parent = study;
     912
     913                                // Try to validate the entity now all fields have been set. If it fails, return an error
     914                                if (!entity.validate() || invalidFields) {
    534915                                        numInvalidEntities++;
    535916
    536917                                        // Add this field to the list of failed cells, in order to give the user feedback
    537                                         failedcells = addNonValidatingCells( failedcells, entity )
     918                                        failedCells = addNonValidatingCells( failedCells, entity )
    538919
    539920                                        // Also create a full list of errors
    540921                                        errors += getHumanReadableErrors( entity );
    541                                 }
    542                         }
    543                 }
     922                                } else {
     923                                        importerService.removeFailedCell( failedCells, entity )
     924                                }
     925                        } // end of record
     926                } // end of table
    544927
    545928                session.simpleWizard.imported.numInvalidEntities = numInvalidEntities;
     
    548931                return true
    549932        }
     933
     934        /**
     935         * Handles assay input
     936         * @param study         Study to update
     937         * @param params                Request parameter map
     938         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
     939         */
     940        def handleAssays( assay, params ) {
     941                // did the study template change?
     942                if (params.get('template') && assay.template?.name != params.get('template')) {
     943                        // set the template
     944                        assay.template = Template.findByName(params.remove('template'))
     945                }
     946
     947                // does the study have a template set?
     948                if (assay.template && assay.template instanceof Template) {
     949                        // yes, iterate through template fields
     950                        assay.giveFields().each() {
     951                                // and set their values
     952                                assay.setFieldValue(it.name, params.get(it.escapedName()))
     953                        }
     954                }
     955
     956                // Save the assay in session
     957                session.simpleWizard.assay = assay;
     958
     959                return true
     960        }
    550961       
    551962        /**
    552         * Handles the update of the edited fields by the user
    553         * @param study          Study to update
    554         * @param params         Request parameter map
    555         * @return                       True if everything went OK, false otherwise. An error message is put in flash.error.
    556         *                                       The field session.simpleWizard.imported.numInvalidEntities reflects the number of
    557         *                                       entities that still have errors, and should be fixed before saving. The errors for those entities
    558         *                                       are saved into session.simpleWizard.imported.errors
    559         */
    560    def handleMissingFields( study, params ) {
    561            def numInvalidEntities = 0;
    562            def errors = [];
    563 
    564            // Remove all samples before adding them again
    565            study.samples?.clear();
    566 
    567            // Check which fields failed previously
    568            def failedCells = session.simpleWizard.imported.failedCells
    569 
    570            session.simpleWizard.imported.data.each { table ->
    571                    table.each { entity ->
    572                            def invalidFields = 0
    573 
    574                            // Set the fields for this entity by retrieving values from the params
    575                            entity.giveFields().each { field ->
    576                                    def fieldName = importerService.getFieldNameInTableEditor( entity, field );
    577 
    578                                    if( params[ fieldName ] == "#invalidterm" ) {
    579                                            // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect
    580                                            invalidFields++;
    581                                    } else {
    582                                            if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) {
    583                                                    // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from
    584                                                    // the failedCells list
    585                                                    importerService.removeFailedCell( failedCells, entity, field )
    586                                            }
    587 
    588                                            // Update the field, regardless of the type of field
    589                                            entity.setFieldValue(field.name, params[ fieldName ] )
    590                                    }
    591                            }
    592 
    593                            // Determine entity class and add a parent
    594                            entity.parent = study;
    595                            study.addToSamples( entity );
    596 
    597                            // Try to validate the entity now all fields have been set. If it fails, return an error
    598                            if (!entity.validate() || invalidFields) {
    599                                    numInvalidEntities++;
    600 
    601                                    // Add this field to the list of failed cells, in order to give the user feedback
    602                                    failedCells = addNonValidatingCells( failedCells, entity )
    603 
    604                                    // Also create a full list of errors
    605                                    errors += getHumanReadableErrors( entity );
    606                            } else {
    607                                    importerService.removeFailedCell( failedCells, entity )
    608                            }
    609                    } // end of record
    610            } // end of table
    611 
    612            session.simpleWizard.imported.numInvalidEntities = numInvalidEntities;
    613            session.simpleWizard.imported.errors = errors;
    614 
    615            return true
    616    }
    617        
    618         /**
    619         * Handles assay input
    620         * @param study          Study to update
    621         * @param params         Request parameter map
    622         * @return                       True if everything went OK, false otherwise. An error message is put in flash.error
    623         */
    624    def handleAssays( assay, params ) {
    625            // did the study template change?
    626            if (params.get('template') && assay.template?.name != params.get('template')) {
    627                    // set the template
    628                    assay.template = Template.findByName(params.remove('template'))
    629            }
    630 
    631            // does the study have a template set?
    632            if (assay.template && assay.template instanceof Template) {
    633                    // yes, iterate through template fields
    634                    assay.giveFields().each() {
    635                            // and set their values
    636                            assay.setFieldValue(it.name, params.get(it.escapedName()))
    637                    }
    638            }
    639 
    640            return true
    641    }
    642 
    643    /**
    644     * Validates an object and puts human readable errors in validationErrors variable
    645     * @param entity             Entity to validate
    646     * @return                   True iff the entity validates, false otherwise
    647     */
    648    protected boolean validateObject( def entity ) {
    649            if( !entity.validate() ) {
    650                    flash.validationErrors = getHumanReadableErrors( entity )
    651                    return false;
    652            }
    653            return true;
    654    }
    655    
    656    
     963         * Checks whether the given study is simple enough to be edited using this controller.
     964         *
     965         * The study is simple enough if the samples, subjects, events and samplingEvents can be
     966         * edited as a flat table. That is:
     967         *              - Every subject belongs to 0 or 1 eventgroup
     968         *              - Every eventgroup belongs to 0 or 1 sample
     969         *              - Every eventgroup has 0 or 1 subjects, 0 or 1 event and 0 or 1 samplingEvents
     970         *              - If a sample belongs to an eventgroup:
     971         *                      - If that eventgroup has a samplingEvent, that same samplingEvent must also be
     972         *                              the sampling event that generated this sample
     973         *                      - If that eventgroup has a subject, that same subject must also be the subject
     974         *                              from whom the sample was taken
     975         *
     976         * @param study         Study to check
     977         * @return                      True if the study can be edited by this controller, false otherwise
     978         */
     979        def checkStudySimplicity( study ) {
     980                def simplicity = true;
     981               
     982                if( !study )
     983                        return false
     984
     985                if( study.eventGroups ) {
     986                        study.eventGroups.each { eventGroup ->
     987                                // Check for simplicity of eventgroups: only 0 or 1 subject, 0 or 1 event and 0 or 1 samplingEvent
     988                                if( eventGroup.subjects?.size() > 1 || eventGroup.events?.size() > 1 || eventGroup.samplingEvents?.size() > 1 ) {
     989                                        flash.message = "One or more eventgroups contain multiple subjects or events."
     990                                        simplicity = false;
     991                                }
     992                               
     993                                // Check whether this eventgroup only belongs to (max) 1 sample
     994                                def numSamples = 0;
     995                                study.samples.each { sample ->
     996                                        // If no id is given for the eventGroup, it has been entered in this wizard, but
     997                                        // not yet saved. In that case, it is always OK
     998                                        if( eventGroup.id && sample.parentEventGroup?.id == eventGroup.id )
     999                                                numSamples++;
     1000                                }
     1001                               
     1002                                if( numSamples > 1 ) {
     1003                                        flash.message = "One or more eventgroups belong to multiple samples."
     1004                                        simplicity = false;
     1005                                }
     1006                        }
     1007                       
     1008                        if( !simplicity ) return false;
     1009
     1010                        // Check whether subject only belong to zero or one event group
     1011                        if( study.subjects ) {
     1012                                study.subjects.each { subject ->
     1013                                        def numEventGroups = 0
     1014                                        study.eventGroups.each { eventGroup ->
     1015                                                // If no id is given for the subject, it has been entered in this wizard, but
     1016                                                // not yet saved. In that case, it is always OK
     1017                                                if( subject.id && eventGroup.subjects[0]?.id == subject.id )
     1018                                                        numEventGroups++
     1019                                        }
     1020                                       
     1021                                        if( numEventGroups > 1 ) {
     1022                                                flash.message = "One or more subjects belong to multiple eventgroups."
     1023                                                simplicity = false;
     1024                                        }
     1025                                }
     1026                        }
     1027
     1028                        if( !simplicity ) return false;
     1029                       
     1030                        // Check whether the samples that belong to an eventgroup have the right parentObjects
     1031                        study.samples.each { sample ->
     1032                                if( sample.parentEventGroup ) {
     1033                                        // If no id is given for the subject, it has been entered in this wizard, but
     1034                                        // not yet saved. In that case, it is always OK
     1035                                        if( sample.parentSubject && sample.parentSubject.id) {
     1036                                                if( !sample.parentEventGroup.subjects || sample.parentEventGroup.subjects[0]?.id != sample.parentSubject.id ) {
     1037                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
     1038                                                        simplicity = false;
     1039                                                }
     1040                                        }
     1041                                       
     1042                                        // If no id is given for the sampling event, it has been entered in this wizard, but
     1043                                        // not yet saved. In that case, it is always OK
     1044                                        if( sample.parentEvent && sample.parentEvent.id) {
     1045                                                if( !sample.parentEventGroup.samplingEvents || sample.parentEventGroup.samplingEvents[0]?.id != sample.parentEvent.id ) {
     1046                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
     1047                                                        simplicity = false;
     1048                                                }
     1049                                        }
     1050                                }
     1051                        }
     1052                       
     1053                        if( !simplicity ) return false;
     1054                }
     1055               
     1056                return simplicity;
     1057        }
     1058
     1059        def attachAndValidateEntities( def study ) {
     1060                if( !session.simpleWizard?.imported?.data )
     1061                        return
     1062                         
     1063                def table = session.simpleWizard.imported.data
     1064                def numInvalidEntities = 0;
     1065               
     1066                // Add all samples
     1067                table.each { record ->
     1068                        println record*.class
     1069                        record.each { entity ->
     1070                                if( entity ) {
     1071                                        if( entity.validate() ) {
     1072                                                println "Saving: " + entity + " (" + entity.class.name + ")"
     1073                                                println study.samples;
     1074                                                println study.subjects;
     1075                                               
     1076                                                // Determine entity class and add a parent
     1077                                                def preferredIdentifier = importerService.givePreferredIdentifier( entity.class );
     1078                                                def equalClosure = { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) }
     1079               
     1080                                                //entity.parent = study
     1081                                                switch( entity.class ) {
     1082                                                        case Sample:
     1083                                                                if( preferredIdentifier && !study.samples?.find( equalClosure ) ) {
     1084                                                                        study.addToSamples( entity );
     1085                                                                }
     1086                                                                break;
     1087                                                        case Subject:
     1088                                                                if( preferredIdentifier && !study.subjects?.find( equalClosure ) ) {
     1089                                                                        study.addToSubjects( entity );
     1090                                                                }
     1091                                                                break;
     1092                                                        case Event:
     1093                                                                if( preferredIdentifier && !study.events?.find( equalClosure ) ) {
     1094                                                                        study.addToEvents( entity );
     1095                                                                }
     1096                                                                break;
     1097                                                        case SamplingEvent:
     1098                                                                if( preferredIdentifier && !study.samplingEvents?.find( equalClosure ) ) {
     1099                                                                        study.addToSamplingEvents( entity );
     1100                                                                }
     1101                                                                break;
     1102                                                }
     1103                                               
     1104                                                entity.save();
     1105                                               
     1106                                        } else {
     1107                                                numInvalidEntities++;
     1108                                        }
     1109                                }
     1110                        }
     1111                }
     1112
     1113                return numInvalidEntities == 0;
     1114        }
     1115               
     1116        /**
     1117         * Validates an object and puts human readable errors in validationErrors variable
     1118         * @param entity                Entity to validate
     1119         * @return                      True iff the entity validates, false otherwise
     1120         */
     1121        protected boolean validateObject( def entity ) {
     1122                if( !entity.validate() ) {
     1123                        flash.validationErrors = getHumanReadableErrors( entity )
     1124                        return false;
     1125                }
     1126                return true;
     1127        }
     1128
    6571129        /**
    6581130         * Adds all fields of this entity that have given an error when validating to the failedcells list
     
    6671139                entity.getErrors().getFieldErrors().each { error ->
    6681140                        String field = error.getField();
     1141                       
    6691142                        def mc = importerService.findMappingColumn( session.simpleWizard.excel.data.header, field );
    6701143                        def mcInstance = new MappingColumn( name: field, entityClass: Sample.class, index: -1, property: field.toLowerCase(), templateFieldType: entity.giveFieldType( field ) );
     
    6811154                return failedcells
    6821155        }
    683 
    684 
    6851156
    6861157
     
    6981169                if( sheetIndex > workbook.getNumberOfSheets() ) {
    6991170                        log.error ".simple study wizard Sheet index is too high: " + sheetIndex + " / " + workbook.getNumberOfSheets();
    700                         flash.error = "The sheet number you provided is too high. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s).";
     1171                        flash.error = "Your excel sheet contains too few excel sheets. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s).";
    7011172                        return false
    7021173                }
     
    7091180                if( headerRow > numRows  ) {
    7101181                        log.error ".simple study wizard Header row number is incorrect: " + headerRow + " / " + numRows;
    711                         flash.error = "The header row number you provided is too high. Please provide a number equal to or below " + numRows;
     1182                        flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
    7121183                        return false
    7131184                }
     
    7151186                if( dataMatrixStart > numRows  ) {
    7161187                        log.error ".simple study wizard Data row number is incorrect: " + dataMatrixStart + " / " + numRows;
    717                         flash.error = "The data row number you provided is too high. Please provide a number equal to or below " + numRows;
     1188                        flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
    7181189                        return false
    7191190                }
     
    7561227                Study s = Study.get( id );
    7571228
     1229                if( !s ) {
     1230                        flash.error = "No study found with given id";
     1231                        return null;
     1232                }
    7581233                if( !s.canWrite( authenticationService.getLoggedInUser() ) ) {
    7591234                        flash.error = "No authorization to edit this study."
     
    7641239        }
    7651240
     1241        /**
     1242         * Attach a template to the session
     1243         * @param t
     1244         * @return
     1245         */
     1246        protected attachTemplate( Template t ) {
     1247                if( t && !t.isAttached() ) {
     1248                        t.attach();
     1249
     1250                        t.fields.each { field ->
     1251                                if( field && !field.isAttached() )
     1252                                        field.attach();
     1253       
     1254                                field.listEntries?.each { entry ->
     1255                                        if( entry && !entry.isAttached() )
     1256                                                entry.attach();
     1257                                }
     1258                                field.ontologies?.each { entry ->
     1259                                        if( entry && !entry.isAttached() )
     1260                                                entry.attach();
     1261                                }
     1262                        }
     1263                }
     1264        }
     1265       
    7661266        /**
    7671267         * Retrieves the study that is saved in the wizard,
     
    7731273                if( params.wizard && session.simpleWizard && session.simpleWizard.study ) {
    7741274                        // The user came here by clicking previous or a link on another page. Use the existing study
    775                         return session.simpleWizard.study;
     1275                        Study s = session.simpleWizard.study;
     1276
     1277                        if( s.id && !s.isAttached() ) {
     1278                                s.attach();
     1279                        }
     1280                       
     1281                        s.samples?.each {
     1282                                if( it && it.id && !it.isAttached() )
     1283                                        it.attach();
     1284                                         
     1285                                attachTemplate( it.template )
     1286                        }
     1287                        s.subjects?.each {
     1288                                if( it && it.id && !it.isAttached() )
     1289                                        it.attach();
     1290                                         
     1291                                attachTemplate( it.template )
     1292                        }
     1293                        s.events?.each {
     1294                                if( it && it.id && !it.isAttached() )
     1295                                        it.attach();
     1296                                         
     1297                                attachTemplate( it.template )
     1298                        }
     1299
     1300                        s.samplingEvents?.each {
     1301                                if( it && it.id && !it.isAttached() )
     1302                                        it.attach();
     1303                                         
     1304                                attachTemplate( it.template )
     1305                        }
     1306                        s.assays?.each {
     1307                                if( it && it.id && !it.isAttached() )
     1308                                        it.attach();
     1309                                         
     1310                                attachTemplate( it.template )
     1311                        }
     1312
     1313                        return s;
    7761314                } else {
    7771315                        // The user didn't get here from the wizard or no study is found
     1316                        return null;
     1317                }
     1318        }
     1319
     1320        /**
     1321         * Retrieves the assay to edit in the wizard
     1322         * @param s             Study that the assay will be in
     1323         * @return              Assay object (may be empty)
     1324         */
     1325        protected Assay getAssayInWizard( Study study = null ) {
     1326                if( session.simpleWizard && session.simpleWizard.assay ) {
     1327                        Assay a = session.simpleWizard.assay;
     1328
     1329                        if( a.id && !a.isAttached() ) {
     1330                                a.attach();
     1331
     1332                                attachTemplate( a.template );
     1333                        }
     1334
     1335                        return a;
     1336                } else if( study ) {
     1337                        // The user came on the assay page for the first time
     1338                        if( study.assays?.size() ) {
     1339                                def assay = study.assays[0];
     1340
     1341                                return assay;
     1342                        } else {
     1343                                return new Assay( parent: study );
     1344                        }
     1345                } else {
    7781346                        return null;
    7791347                }
     
    8021370                return errors
    8031371        }
    804 
    8051372}
  • trunk/grails-app/services/dbnp/importer/ImporterService.groovy

    r1588 r1591  
    1515 */
    1616package dbnp.importer
     17
    1718import org.dbnp.gdt.*
    1819import org.apache.poi.ss.usermodel.*
     
    186187        }
    187188
     189       
     190        /**
     191         * Retrieves records with sample, subject, samplingevent etc. from a study
     192         * @param s             Study to retrieve records from
     193         * @return              A list with hashmaps [ 'objects': [ 'Sample': .., 'Subject': .., 'SamplingEvent': .., 'Event': '.. ], 'templates': [], 'templateCombination': .. ]
     194         */
     195        protected def getRecords( Study s ) {
     196                def records = [];
     197               
     198                s.samples?.each {
     199                        def record = [ 'objects': retrieveEntitiesBySample( it ) ];
     200               
     201                        def templates = [:]
     202                        def templateCombination = [];
     203                        record.objects.each { entity ->
     204                                templates[ entity.key ] = entity.value?.template
     205                                if( entity.value?.template )
     206                                        templateCombination << entity.key + ": " + entity.value?.template?.name;
     207                        }
     208                       
     209                        record.templates = templates;
     210                        record.templateCombination = templateCombination.join( ', ' )
     211                       
     212                        records << record
     213                }
     214               
     215                return records;
     216        }
     217       
     218        /**
     219         * Returns a subject, event and samplingEvent that belong to this sample
     220         * @param s             Sample to find the information for
     221         * @return
     222         */
     223        protected retrieveEntitiesBySample( Sample s ) {
     224                return [
     225                        'Sample': s,
     226                        'Subject': s?.parentSubject,
     227                        'SamplingEvent': s?.parentEvent,
     228                        'Event': s?.parentEventGroup?.events?.getAt(0)
     229                ]
     230        }
     231       
     232        /**
     233         * Imports data from a workbook into a list of ImportRecords. If some entities are already in the database,
     234         * these records are updated.
     235         *
     236         * This method is capable of importing Subject, Samples, SamplingEvents and Events
     237         *
     238         * @param       templates       Map of templates, identified by their entity as a key. For example: [ Subject: Template x, Sample: Template y ]
     239         * @param       wb                      Excel workbook to import
     240         * @param       sheetindex      Number of the sheet to import data from
     241         * @param       rowindex        Row to start importing from.
     242         * @param       mcmap           Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc.
     243         * @param       parent          Study to import all data into. Is used for determining which sample/event/subject/assay to update
     244         * @param       createAllEntities       If set to true, the system will also create objects for entities that have no data imported, but do have
     245         *                                                              a template assigned
     246         * @return      List            List with two entries:
     247         *                      0                       List with ImportRecords, one for each row in the excelsheet
     248         *                      1                       List with ImportCell objects, mentioning the cells that could not be correctly imported
     249         *                                              (because the value in the excelsheet can't be entered into the template field)
     250         */
     251        def importOrUpdateDataBySampleIdentifier( def templates, Workbook wb, int sheetindex, int rowindex, def mcmap, Study parent = null, boolean createAllEntities = true ) {
     252                if( !mcmap )
     253                        return;
     254                       
     255                // Check whether the rows should be imported in one or more entities
     256                def entities
     257                if( createAllEntities ) {
     258                        entities = templates.entrySet().value.findAll { it }.entity;
     259                } else {
     260                        entities = mcmap.findAll{ !it.dontimport }.entityclass.unique();
     261                }
     262               
     263                def sheet = wb.getSheetAt(sheetindex)
     264                def table = []
     265                def failedcells = [] // list of cells that have failed to import
     266
     267                // First check for each record whether an entity in the database should be updated,
     268                // or a new entity should be added. This is done before any new object is created, since
     269                // searching after new objects have been created (but not yet saved) will result in
     270                //      org.hibernate.AssertionFailure: collection [...] was not processed by flush()
     271                // errors
     272                def existingEntities = [:]
     273                for( int i = rowindex; i <= sheet.getLastRowNum(); i++ ) {
     274                        existingEntities[i] = findExistingEntities( entities, sheet.getRow(i), mcmap, parent );
     275                }
     276                               
     277                // walk through all rows and fill the table with records
     278                for( int i = rowindex; i <= sheet.getLastRowNum(); i++ ) {
     279                        // Create an entity record based on a row read from Excel and store the cells which failed to be mapped
     280                        def (record, failed) = importOrUpdateRecord( templates, entities, sheet.getRow(i), mcmap, parent, table, existingEntities[i] );
     281                       
     282                        // Setup the relationships between the imported entities
     283                        relateEntities( record );
     284                       
     285                        // Add record with entities and its values to the table
     286                        table.add(record)
     287
     288                        // If failed cells have been found, add them to the failed cells list
     289                        if (failed?.importcells?.size() > 0) failedcells.add(failed)
     290                }
     291               
     292                return [ "table": table, "failedCells": failedcells ]
     293        }
     294
     295        /**
     296        * Checks whether entities in the given row already exist in the database
     297        * they are updated.
     298        *
     299        * @param        entities        Entities that have to be imported for this row
     300        * @param        excelRow        Excel row to import into this record
     301        * @param        mcmap           Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc.
     302        * @return       Map                     Map with entities that have been found for this row. The key for the entities is the entity name (e.g.: [Sample: null, Subject: <subject object>]
     303        */
     304   def findExistingEntities(def entities, Row excelRow, mcmap, parent ) {
     305           DataFormatter df = new DataFormatter();
     306
     307           // Find entities based on sample identifier
     308           def sample = findEntityByRow( dbnp.studycapturing.Sample, excelRow, mcmap, parent, [], df );
     309           return retrieveEntitiesBySample( sample );
     310   }
     311   
     312        /**
     313         * Imports a records from the excelsheet into the database. If the entities are already in the database
     314         * they are updated.
     315         *
     316         * This method is capable of importing Subject, Samples, SamplingEvents and Events
     317         *
     318         * @param       templates       Map of templates, identified by their entity as a key. For example: [ Sample: Template y ]
     319         * @param       entities        Entities that have to be imported for this row
     320         * @param       excelRow        Excel row to import into this record
     321         * @param       mcmap           Hashmap of mappingcolumns, with the first entry in the hashmap containing information about the first column, etc.
     322         * @param       parent          Study to import all data into. Is used for determining which sample/event/subject/assay to update
     323         * @param       importedRows    Rows that have been imported before this row. These rows might contain the same entities as are
     324         *                                                      imported in this row. These entities should be used again, to avoid importing duplicates.
     325         * @return      List            List with two entries:
     326         *                      0                       List with ImportRecords, one for each row in the excelsheet
     327         *                      1                       List with ImportCell objects, mentioning the cells that could not be correctly imported
     328         *                                              (because the value in the excelsheet can't be entered into the template field)
     329         */
     330        def importOrUpdateRecord(def templates, def entities, Row excelRow, mcmap, Study parent = null, List importedRows, Map existingEntities ) {
     331                DataFormatter df = new DataFormatter();
     332                def record = [] // list of entities and the read values
     333                def failed = new ImportRecord() // map with entity identifier and failed mappingcolumn
     334       
     335                // Check whether this record mentions a sample that has been imported before. In that case,
     336                // we update that record, in order to prevent importing the same sample multiple times
     337                def importedEntities = [];
     338                if( importedRows )
     339                        importedEntities = importedRows.flatten().findAll { it.class == dbnp.studycapturing.Sample }.unique();
     340
     341                def importedSample = findEntityInImportedEntities( dbnp.studycapturing.Sample, excelRow, mcmap, importedEntities, df )
     342                def imported = retrieveEntitiesBySample( importedSample );
     343               
     344                for( entity in entities ) {
     345                        // Check whether this entity should be added or updated
     346                        // The entity is updated is an entity with the same 'identifier' (field
     347                        // specified to be the identifying field) is found in the database
     348                        def entityName = entity.name[ entity.name.lastIndexOf( '.' ) + 1..-1];
     349                        def template = templates[ entityName ];
     350                       
     351                        // If no template is specified for this entity, continue with the next
     352                        if( !template )
     353                                continue;
     354                       
     355                        // Check whether the object exists in the list of already imported entities
     356                        def entityObject = imported[ entityName ]
     357                       
     358                        // If it doesn't, search for the entity in the database
     359                        if( !entityObject && existingEntities )
     360                                entityObject = existingEntities[ entityName ];
     361                       
     362                        // Otherwise, create a new object
     363                        if( !entityObject )
     364                                entityObject = entity.newInstance();
     365                       
     366                        // Update the template
     367                        entityObject.template = template;
     368                       
     369                        // Go through the Excel row cell by cell
     370                        for (Cell cell: excelRow) {
     371                                // get the MappingColumn information of the current cell
     372                                def mc = mcmap[cell.getColumnIndex()]
     373                                def value
     374         
     375                                // Check if column must be imported
     376                                if (mc != null && !mc.dontimport && mc.entityclass == entity) {
     377                                        try {
     378                                                value = formatValue(df.formatCellValue(cell), mc.templatefieldtype)
     379                                        } catch (NumberFormatException nfe) {
     380                                                value = ""
     381                                        }
     382         
     383                                        try {
     384                                                entityObject.setFieldValue(mc.property, value)
     385                                        } catch (Exception iae) {
     386                                                log.error ".import wizard error could not set property `" + mc.property + "` to value `" + value + "`"
     387
     388                                                // store the mapping column and value which failed
     389                                                def identifier = entityName.toLowerCase() + "_" + entityObject.getIdentifier() + "_" + mc.property
     390         
     391                                                def mcInstance = new MappingColumn()
     392                                                mcInstance.properties = mc.properties
     393                                                failed.addToImportcells(new ImportCell(mappingcolumn: mcInstance, value: value, entityidentifier: identifier))
     394                                        }
     395                                } // end if
     396                        } // end for
     397                       
     398                        // If a Study is entered, use it as a 'parent' for other entities
     399                        if( entity == Study )
     400                                parent = entityObject;
     401                       
     402                        record << entityObject;
     403                }
     404               
     405                // a failed column means that using the entity.setFieldValue() threw an exception
     406                return [record, failed]
     407        }
     408       
     409        /**
     410         * Looks into the database to find an object of the given entity that should be updated, given the excel row.
     411         * This is done by looking at the 'preferredIdentifier' field of the object. If it exists in the row, and the
     412         * value is already in the database for that field, an existing object is returned. Otherwise, null is returned
     413         *
     414         * @param       entity          Entity to search
     415         * @param       excelRow        Excelrow to search for
     416         * @param       mcmap           Map with MappingColumns
     417         * @param       parent          Parent study for the entity (if applicable). The returned entity will also have this parent
     418         * @param       importedRows    List of entities that have been imported before. The function will first look through this list to find
     419         *                                                      a matching entity.
     420         * @return      An entity that has the same identifier as entered in the excelRow. The entity is first sought in the importedRows. If it
     421         *                      is not found there, the database is queried. If no entity is found at all, null is returned.
     422         */
     423        def findEntityByRow( Class entity, Row excelRow, def mcmap, Study parent = null, List importedEntities = [], DataFormatter df = null ) {
     424                if( df == null )
     425                        df = new DataFormatter();
     426                       
     427                def identifierField = givePreferredIdentifier( entity );
     428               
     429                if( identifierField ) {
     430                        // Check whether the identifierField is chosen in the column matching
     431                        def identifierColumn = mcmap.find { it.entityclass == entity && it.property == identifierField.name };
     432                       
     433                        // If it is, find the identifier and look it up in the database
     434                        if( identifierColumn ) {
     435                                def identifierCell = excelRow.getCell( identifierColumn.index );
     436                                def identifier;
     437                                try {
     438                                        identifier = formatValue(df.formatCellValue(identifierCell), identifierColumn.templatefieldtype)
     439                                } catch (NumberFormatException nfe) {
     440                                        identifier = null
     441                                }
     442                               
     443                                // Search for an existing object with the same identifier.
     444                                if( identifier ) {
     445                                        // First search the already imported rows
     446                                        if( importedEntities ) {
     447                                                def imported = importedEntities.find { it.getFieldValue( identifierField.name ) == identifier };
     448                                                if( imported )
     449                                                        return imported;
     450                                        }
     451                                       
     452                                        def c = entity.createCriteria();
     453                                       
     454                                        // If the entity has a field 'parent', the search should be limited to
     455                                        // objects with the same parent. The method entity.hasProperty( "parent" ) doesn't
     456                                        // work, since the java.lang.Class entity doesn't know of the parent property.
     457                                        if( entity.belongsTo?.containsKey( "parent" ) ) {
     458                                                // If the entity requires a parent, but none is given, no
     459                                                // results are given from the database. This prevents the user
     460                                                // of changing data in another study
     461                                                if( parent && parent.id ) {
     462                                                        println "Searching (with parent ) for " + entity.name + " with " + identifierField.name + " = " + identifier
     463                                                        return c.get {
     464                                                                eq( identifierField.name, identifier )
     465                                                                eq( "parent", parent )
     466                                                        }
     467                                                }
     468                                        } else  {
     469                                                println "Searching (without parent ) for " + entity.name + " with " + identifierField.name + " = " + identifier
     470                                                return c.get {
     471                                                        eq( identifierField.name, identifier )
     472                                                }
     473                                        }
     474                                }
     475                        }
     476                }
     477               
     478                // No object is found
     479                return null;
     480        }
     481       
     482        /**
     483        * Looks into the list of already imported entities to find an object of the given entity that should be
     484        * updated, given the excel row. This is done by looking at the 'preferredIdentifier' field of the object.
     485        * If it exists in the row, and the list of imported entities contains an object with the same
     486        * identifier, the existing object is returned. Otherwise, null is returned
     487        *
     488        * @param        entity          Entity to search
     489        * @param        excelRow        Excelrow to search for
     490        * @param        mcmap           Map with MappingColumns
     491        * @param        importedRows    List of entities that have been imported before. The function will first look through this list to find
     492        *                                                       a matching entity.
     493        * @return       An entity that has the same identifier as entered in the excelRow. The entity is first sought in the importedRows. If it
     494        *                       is not found there, the database is queried. If no entity is found at all, null is returned.
     495        */
     496   def findEntityInImportedEntities( Class entity, Row excelRow, def mcmap, List importedEntities = [], DataFormatter df = null ) {
     497           if( df == null )
     498                   df = new DataFormatter();
     499                   
     500           def allFields = entity.giveDomainFields();
     501           def identifierField = allFields.find { it.preferredIdentifier }
     502           
     503           if( identifierField ) {
     504                   // Check whether the identifierField is chosen in the column matching
     505                   def identifierColumn = mcmap.find { it.entityclass == entity && it.property == identifierField.name };
     506                   
     507                   // If it is, find the identifier and look it up in the database
     508                   if( identifierColumn ) {
     509                           def identifierCell = excelRow.getCell( identifierColumn.index );
     510                           def identifier;
     511                           try {
     512                                   identifier = formatValue(df.formatCellValue(identifierCell), identifierColumn.templatefieldtype)
     513                           } catch (NumberFormatException nfe) {
     514                                   identifier = null
     515                           }
     516                           
     517                           // Search for an existing object with the same identifier.
     518                           if( identifier ) {
     519                                        // First search the already imported rows
     520                                        if( importedEntities ) {
     521                                                def imported = importedEntities.find {
     522                                                        def fieldValue = it.getFieldValue( identifierField.name )
     523
     524                                                        if( fieldValue instanceof String )
     525                                                                return fieldValue.toLowerCase() == identifier.toLowerCase();
     526                                                        else
     527                                                                return fieldValue == identifier
     528
     529                                                };
     530                                           if( imported )
     531                                                   return imported;
     532                                   }
     533                           }
     534                   }
     535           }
     536           
     537           // No object is found
     538           return null;
     539   }
     540
     541       
     542        /**
     543         * Creates relation between multiple entities that have been imported. The entities are
     544         * all created from one row in the excel sheet.
     545         */
     546        def relateEntities( List entities) {
     547                def study = entities.find { it instanceof Study }
     548                def subject = entities.find { it instanceof Subject }
     549                def sample = entities.find { it instanceof Sample }
     550                def event = entities.find { it instanceof Event }
     551                def samplingEvent = entities.find { it instanceof SamplingEvent }
     552                def assay = entities.find { it instanceof Assay }
     553               
     554                // A study object is found in the entity list
     555                if( study ) {
     556                        if( subject ) {
     557                                subject.parent = study;
     558                                study.addToSubjects( subject );
     559                        }
     560                        if( sample ) {
     561                                sample.parent = study
     562                                study.addToSamples( sample );
     563                        }
     564                        if( event ) {
     565                                event.parent = study
     566                                study.addToEvents( event );
     567                        }
     568                        if( samplingEvent ) {
     569                                samplingEvent.parent = study
     570                                study.addToSamplingEvents( samplingEvent );
     571                        }
     572                        if( assay ) {
     573                                assay.parent = study;
     574                                study.addToAssays( assay );
     575                        }
     576                }
     577
     578                if( sample ) {
     579                        if( subject ) sample.parentSubject = subject
     580                        if( samplingEvent ) sample.parentEvent = samplingEvent;
     581                        if( event ) {
     582                                def evGroup = new EventGroup();
     583                                evGroup.addToEvents( event );
     584                                if( subject ) evGroup.addToSubjects( subject );
     585                                if( samplingEvent ) evGroup.addToSamplingEvents( samplingEvent );
     586                               
     587                                sample.parentEventGroup = evGroup;
     588                        }
     589                       
     590                        if( assay ) assay.addToSamples( sample );
     591                }
     592        }
     593
    188594        /**
    189595         * Method to read data from a workbook and to import data into a two dimensional
     
    258664         */
    259665        def getFieldNameInTableEditor(entity, field) {
     666                def entityName = entity?.class.name[ entity?.class.name.lastIndexOf(".") + 1..-1]
     667               
    260668                if( field instanceof TemplateField )
    261669                        field = field.escapedName();
    262670
    263                 return "entity_" + entity.getIdentifier() + "_" + field
     671                return entityName.toLowerCase() + "_" + entity.getIdentifier() + "_" + field
    264672        }
    265673
     
    562970                }
    563971        }
     972       
     973        /**
     974         * Returns the preferred identifier field for a given entity or
     975         * null if no preferred identifier is given
     976         * @param entity        TemplateEntity class
     977         * @return      The preferred identifier field or NULL if no preferred identifier is given
     978         */
     979        public TemplateField givePreferredIdentifier( Class entity ) {
     980                def allFields = entity.giveDomainFields();
     981                return allFields.find { it.preferredIdentifier }
     982        }
    564983
    565984        // classes for fuzzy string matching
  • trunk/grails-app/views/simpleWizard/assays.gsp

    r1581 r1591  
    3838                        <input type="hidden" name="wizard" value="true" />
    3939                        <input type="hidden" name="event" value="refresh" />
    40                
    4140                        <af:templateElement name="template" description="Template"
    42                                 value="${assay?.template}" entity="${dbnp.studycapturing.Assay}"
     41                                value="${wizardAssay?.template}" entity="${dbnp.studycapturing.Assay}"
    4342                                addDummy="true" onChange="if(\$( this ).val() != '') { submitForm( 'assays' ); }">
    4443                                Choose the type of assay you would like to perform.
     
    4645                        </af:templateElement>
    4746               
    48                         <g:if test="${assay}">
    49                                 <af:templateElements ignore="externalassayid" entity="${assay}" />
     47                        <g:if test="${wizardAssay}">
     48                                <af:templateElements ignore="externalassayid" entity="${wizardAssay}" />
    5049                        </g:if>
    5150               
  • trunk/grails-app/views/simpleWizard/columns.gsp

    r1581 r1591  
    4343                                        <tr class="matchWith">
    4444                                                <g:each in="${excel.data.header}" var="mappingcolumn" status="i">
     45                                                        <%
     46                                                                def selectedValue;
     47                                                                if( mappingcolumn.entityclass?.name && mappingcolumn.property )
     48                                                                        selectedValue = mappingcolumn.entityclass.name[ mappingcolumn.entityclass.name.lastIndexOf( "." ) + 1 .. -1 ] + mappingcolumn.property;
     49                                                        %>
    4550                                                        <td>
    4651                                                                <g:set var="selected" value="${mappingcolumn.property}"/>
    47                                                                 <importer:propertyChooser name="matches" mappingcolumn="${mappingcolumn}" matchvalue="${mappingcolumn.name}"
    48                                                                         selected="${selected}" fuzzymatching="true" treshold="0.8" template_id="${template.id}" "allfieldtypes="true"/>
     52                                                                <% /* Put a select box with template fields of multiple entities */ %>
     53                                                                <select name="matches.index.${mappingcolumn.index}" style="font-size: 10px;">
     54                                                                        <option value="dontimport">Don't import</option>
     55                                                                        <g:each in="${templates}" var="entityTemplates">
     56                                                                                <g:if test="${entityTemplates.value}">
     57                                                                                        <optgroup label="${entityTemplates.key}">
     58                                                                                                <%
     59                                                                                                        def allFields = domainFields[ entityTemplates.key ] + entityTemplates.value?.fields;
     60                                                                                                %>
     61                                                                                                <g:each in="${allFields}" var="field">
     62                                                                                                        <%
     63                                                                                                                def value = entityTemplates.key + "||" + field.name
     64                                                                                                                def selected = ( value == selectedValue );
     65                                                                                                        %>
     66                                                                                                        <option value="${value}" <g:if test="${selected}">selected="selected"</g:if>>
     67                                                                                                                ${field.name} <g:if test="${field.preferredIdentifier}">[identifier]</g:if>
     68                                                                                                       
     69                                                                                                </g:each>
     70                                                                                        </optgroup>
     71                                                                                </g:if>
     72                                                                        </g:each>
     73                                                                </select>
    4974                                                        </td>
    5075                                                </g:each>
  • trunk/grails-app/views/simpleWizard/missingFields.gsp

    r1581 r1591  
    4747                       
    4848                        <div class="wizard" id="wizard">
    49                             <div class="tableEditor">
    50                                 <g:set var="showHeader" value="${true}"/>
    51                                     <g:each status="index" var="table" in="${imported.data}">
    52                                                 <g:each status="i" var="entity" in="${table}">
    53                                                     <g:if test="${showHeader}">
    54                                                                 <g:set var="showHeader" value="${false}"/>
    55                                                                 <div class="header">
    56                                                                         <div class="firstColumn"></div>
    57                                                                         <af:templateColumnHeaders entity="${entity}" class="column" />
    58                                                                 </div>
    59                                                                 <input type="hidden" name="entity" value="${entity.getClass().getName()}">
    60                                                     </g:if>
    61                                                     <div class="row">
     49                                <g:set var="showHeader" value="${true}" />
     50                                <g:set var="previousTemplate" value=""/>                       
     51                                <div class="tableEditor">
     52                                        <g:each var="record" in="${records}">
     53                                                <g:if test="${showHeader}">
     54                                                        <g:set var="showHeader" value="${false}" />
     55                                                        <div class="header">
    6256                                                                <div class="firstColumn"></div>
    63                                                                 <af:templateColumns id="${entity.hashCode()}" entity="${entity}" template="${entity.template}" name="entity_${entity.getIdentifier()}" class="column" subject="${entity.hashCode()}" addDummy="true" />
    64                                                     </div>
    65                                                 </g:each>
    66                                     </g:each>
    67                             </div>
    68                             <div class="sliderContainer">
    69                               <div class="slider"></div>
    70                             </div>
     57                                                                <g:each var="entity" in="${record}">
     58                                                                        <g:if test="${entity.value}">
     59                                                                                <af:templateColumnHeaders entity="${entity.value}" class="column" columnWidths="[Name:100]"/>
     60                                                                        </g:if>
     61                                                                </g:each>
     62                                                        </div>
     63                                                </g:if>
     64                                                <div class="row">
     65                                                        <div class="firstColumn"></div>
     66                                                        <g:each var="entity" in="${record}">
     67                                                                <g:if test="${entity.value}">
     68                                                                        <af:templateColumns  id="${entity.value.hashCode()}" name="${entity.key.toLowerCase()}_${entity.value.getIdentifier()}" template="${entity.value.template}" class="column" id="1" entity="${entity.value}" addDummy="true" subject="${entity.hashCode()}" />
     69                                                                </g:if>
     70                                                        </g:each>
     71                                                </div>
     72                                        </g:each>
     73                                </div>
     74                                <div class="sliderContainer">
     75                                        <div class="slider" ></div>
     76                                </div>
    7177                    <div>
    7278                </g:form>
  • trunk/grails-app/views/simpleWizard/overview.gsp

    r1581 r1591  
    8989               
    9090                <div class="element">
    91                         <div class="description">Sample template</div>${study.samples?.getAt(0)?.template?.name}
     91                        <div class="description">Samples</div>
     92                       
     93                        <g:if test="${study.samples?.size()}">
     94                                <g:each in="${study.samples.template.unique()}" var="currentSampleTemplate" status="j">
     95                                        <g:if test="${currentSampleTemplate}">
     96                                                <g:if test="${j > 0}">,</g:if>
     97                                                <%=study.samples.findAll { return it.template == currentSampleTemplate; }.size()%>
     98                                                ${currentSampleTemplate.name}
     99                                        </g:if>
     100                                </g:each>
     101                        </g:if>
     102                        <g:else>
     103                                -
     104                        </g:else>
    92105                </div>
    93                 <div class="element">
    94                         <div class="description"># samples</div>${study.samples?.size()}
    95                 </div>
    96                 <div class="element">
    97                         <div class="description">Assay template</div>${study.assays?.getAt(0)?.template?.name}
    98                 </div>
     106               
     107                <br />
     108                <g:if test="${wizardAssay}">
     109                        <div class="element">
     110                                <div class="description">Assay template</div>${wizardAssay.template?.name}
     111                        </div>
     112                </g:if>
    99113
    100114                <br clear="all" />
  • trunk/grails-app/views/simpleWizard/samples.gsp

    r1581 r1591  
    2727                        <input type="hidden" name="wizard" value="true" />
    2828                        <input type="hidden" name="event" value="refresh" />
    29                
    30                         <g:if test="${study.samples?.size()}">
    31                                 <p>Current samples</p>
    32                                 <g:each in="${study.samples}" var="sample">
    33                                         ${sample.name}<br />
    34                                 </g:each>
    35                                
    36                                 <p class="options">
    37                                         <a href="#" onClick="$('#samplesDialog').dialog('open'); return false;" class="add">Add samples</a>
    38                                         <a href="#" onClick="$('#samplesDialog').dialog('open'); return false;" class="update">Update samples</a>
    39                                 </p>
    40                                
    41                                 <% /* If samples are already present, the dialog should not be opened by default */ %>
    42                                 <script type="text/javascript">
    43                                         $( '#samplesDialog' ).dialog({
    44                                                 title   : "Add/update samples",
    45                                                 autoOpen: false,
    46                                                 width   : 800,
    47                                                 height  : 400,
    48                                                 modal   : true,
    49                                                 position: "center",
    50                                                 buttons : {
    51                                                         Add  : function() {
    52                                                                 //addUser(element_id);
    53                                                                 $(this).dialog("close");
    54                                                         },
    55                                                         Update  : function() {
    56                                                                 //addUser(element_id);
    57                                                                 $(this).dialog("close");
    58                                                         },
    59                                                         Close  : function() {
    60                                                                 $(this).dialog("close");
    61                                                         }
    62                                                 },
    63                                                 close   : function() {
    64                                                         /* closeFunc(this); */
    65                                                 }
    66                                         });             
    67                                 </script>
    68                         </g:if>
     29
    6930                        <div id="samplesDialog">
    7031                        <span class="info">
    7132                                        <span class="title">Import sample data</span>
    72                                         You can import your Excel data to the server by choosing a file from your local harddisk in the form below.
     33                                        You can import your Excel data to the server by choosing a file from your local harddisk in the form below. The excel sheet should contain
     34                                        data on the first sheet, and the sheet should contain one row with headers.
    7335                                </span>
    7436                   
     
    8345                                        </tr>
    8446                                        <tr>
    85                                             <td width="100px">
    86                                                 Use data from sheet:
    87                                             </td>
    88                                             <td width="100px">
    89                                                 <g:select name="sheetindex" from="${1..25}" value="${sampleForm?.sheetIndex}"/>
    90                                             </td>
    91                                         </tr>
    92                                         <tr>
    93                                             <td width="100px">
    94                                                 Columnheader starts at row:
    95                                             </td>
    96                                             <td width="100px">
    97                                                 <g:select name="headerrow" from="${1..10}" value="${sampleForm?.headerRow}"/>
    98                                             </td>
    99                                         </tr>
    100                                         <tr>
    101                                             <td width="100px">
    102                                                 Data starts at row:
    103                                             </td>
    104                                             <td width="100px">
    105                                                 <g:select name="datamatrix_start" from="${2..10}" value="${sampleForm?.dataMatrixStart}"/>
    106                                             </td>
    107                                         </tr>   
    108                                         <tr>
    10947                                            <td>
    11048                                                <div id="datatemplate">Choose type of sample template:</div>
    11149                                            </td>
    11250                                            <td>
    113                                                         <g:select rel="template" entity="${encodedEntity}" name="template_id" optionKey="id" optionValue="name" from="${sampleTemplates}" value="${sampleForm?.templateId}"/>
     51                                                        <g:select rel="template" entity="${encodedEntity.Sample}" name="sample_template_id" optionKey="id" optionValue="name" from="${templates.Sample}"/>
    11452                                            </td>
    11553                                        </tr>
    11654                                </table>       
    117                        
    11855                        </div>
    11956               
  • trunk/grails-app/views/simpleWizard/save.gsp

    r1581 r1591  
    2222                </g:if>         
    2323
    24                 ${study}
    25                
    26                 <g:form class="simpleWizard" name="saved" action="saved" controller="simpleWizard">
     24                <g:form class="simpleWizard" name="saved" action="save" controller="simpleWizard">
    2725                        <input type="hidden" name="wizard" value="true" />
    2826                        <input type="hidden" name="event" value="refresh" />
    2927               
    3028                        <p class="options">
    31                                 <g:link class="view" controller="study" action="show" id="${study.id}">View study</g:link>
    32                                 <g:link class="edit" controller="simpleWizard" action="study" id="${study.id}">Edit study</g:link>
    33                                 <g:link class="restart" controller="simpleWizard" action="study">Add another study</g:link>
     29                                <g:if test="${success}">
     30                                        <g:link class="view" controller="study" action="show" id="${study.id}">View study</g:link>
     31                                        <g:link class="edit" controller="simpleWizard" action="study" id="${study.id}">Edit study</g:link>
     32                                        <g:link class="restart" controller="simpleWizard" action="study">Add another study</g:link>
     33                                </g:if>
     34                                <g:else>
     35                                        <a href="#" onClick="submitForm( 'saved', 'previous' ); return false;" class="previous">Previous</a>
     36                                </g:else>
    3437                        </p>
    3538                       
  • trunk/grails-app/views/studyWizard/pages/_samples.gsp

    r1461 r1591  
    3636
    3737        <g:if test="${study.samples}">
    38                 <g:if test="${study.samples.findAll{!it.template}.size()}">
     38                <g:if test="${study.samples.findAll{!it?.template}.size()}">
    3939                <h1>Samples that still need to have a template assigned</h1>
    4040                <div class="tableEditor">
     
    4848                <g:set var="previousTemplate" value=""/>
    4949                <g:each var="sample" in="${study.samples}">
    50                         <g:if test="${!sample.template}">
     50                        <g:if test="${!sample?.template}">
    5151                                <div class="row">
    5252                                        <div class="firstColumn">&nbsp;</div>
    5353                                        <div class="column">
    54                                                 <g:if test="${previousTemplate != sample.parentEvent.template.name}">
    55                                                         <g:set var="previousTemplate" value="${sample.parentEvent.template.name}"/>
    56                                                         ${sample.parentEvent.template.name}
     54                                                <g:if test="${previousTemplate != sample.parentEvent?.template?.name}">
     55                                                        <g:set var="previousTemplate" value="${sample.parentEvent?.template?.name}"/>
     56                                                        ${sample.parentEvent?.template?.name}
    5757                                                        <div class="helpIcon"></div>
    5858                                                        <div class="helpContent">
    59                                                                 <h1>${sample.parentEvent.template.name}</h1>
     59                                                                <h1>${sample.parentEvent?.template?.name}</h1>
    6060                                                                <h2>Template Fields:</h2>
    61                                                                 <g:each var="field" in="${sample.parentEvent.giveFields()}">
     61                                                                <g:each var="field" in="${sample.parentEvent?.giveFields()}">
    6262                                                                        ${field.name[0].toUpperCase() + field.name.substring(1)}<br/>
    6363                                                                </g:each>
     
    6666                                        </div>
    6767                                        <div class="column">
    68                                                 ${sample.parentSubject.name}
     68                                                ${sample.parentSubject?.name}
    6969                                                <div class="helpIcon"></div>
    7070                                                <div class="helpContent">
    71                                                         <h1>${sample.parentSubject.template.name}</h1>
     71                                                        <h1>${sample.parentSubject?.template?.name}</h1>
    7272                                                        <h2>Template Fields:</h2>
    73                                                         <g:each var="field" in="${sample.parentSubject.giveFields()}">
     73                                                        <g:each var="field" in="${sample.parentSubject?.giveFields()}">
    7474                                                                ${field.name[0].toUpperCase() + field.name.substring(1)}<br/>
    7575                                                        </g:each>
     
    109109                                        <div class="firstColumn"></div>
    110110                                        <div class="column">
    111                                                 <g:if test="${previousTemplate != sample.parentEvent.template.name}">
    112                                                         <g:set var="previousTemplate" value="${sample.parentEvent.template.name}"/>
    113                                                         ${sample.parentEvent.template.name}
     111                                                <g:if test="${previousTemplate != sample.parentEvent?.template?.name}">
     112                                                        <g:set var="previousTemplate" value="${sample.parentEvent?.template?.name}"/>
     113                                                        ${sample.parentEvent?.template?.name}
    114114                                                        <div class="helpIcon"></div>
    115115                                                        <div class="helpContent">
    116                                                                 <h1>${sample.parentEvent.template.name}</h1>
     116                                                                <h1>${sample.parentEvent?.template?.name}</h1>
    117117                                                                <h2>Template Fields:</h2>
    118                                                                 <g:each var="field" in="${sample.parentEvent.giveFields()}">
     118                                                                <g:each var="field" in="${sample.parentEvent?.giveFields()}">
    119119                                                                        ${field.name[0].toUpperCase() + field.name.substring(1)}<br/>
    120120                                                                </g:each>
     
    123123                                        </div>
    124124                                        <div class="column">
    125                                                 ${sample.parentSubject.name}
     125                                                ${sample.parentSubject?.name}
    126126                                                <div class="helpIcon"></div>
    127127                                                <div class="helpContent">
    128                                                         <h1>${sample.parentSubject.template.name}</h1>
     128                                                        <h1>${sample.parentSubject?.template?.name}</h1>
    129129                                                        <h2>Template Fields:</h2>
    130                                                         <g:each var="field" in="${sample.parentSubject.giveFields()}">
     130                                                        <g:each var="field" in="${sample.parentSubject?.giveFields()}">
    131131                                                                ${field.name[0].toUpperCase() + field.name.substring(1)}<br/>
    132132                                                        </g:each>
     
    134134                                        </div>
    135135                                        <div class="column">
    136                                                 <af:templateSelect name="template_${sample.getIdentifier()}" entity="${dbnp.studycapturing.Sample}" value="${sample.template}" addDummy="true" tableEditorChangeEvent="switchTemplate(element);" />
     136                                                <af:templateSelect name="template_${sample.getIdentifier()}" entity="${dbnp.studycapturing.Sample}" value="${sample?.template}" addDummy="true" tableEditorChangeEvent="switchTemplate(element);" />
    137137                                        </div>
    138138                                        <af:templateColumns name="sample_${sample.getIdentifier()}" class="column" id="1" entity="${sample}"/>
Note: See TracChangeset for help on using the changeset viewer.