From eee9182a5d0f0074d949ae6461d0f58a68a18473 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 5 Dec 2008 02:53:54 +0000 Subject: [PATCH 01/90] Created survey-rfe from rev [8821] of WebGUI trunk From 4fe81a69f61904c064fd73fc9eb74445826fa9a2 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 15 Dec 2008 03:42:39 +0000 Subject: [PATCH 02/90] Implemented Survey Jump-to combo box (#9202) You will need to manually import survey_rfe_9202 until this branch is merged back into trunk. --- lib/WebGUI/Asset/Wobject/Survey.pm | 8 +++++++- survey_rfe_9202.wgpkg | Bin 0 -> 8874 bytes www/extras/wobject/Survey/editsurvey.js | 8 +++++++- www/extras/wobject/Survey/surveyedit.css | 5 ++++- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 survey_rfe_9202.wgpkg diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index a8ed89836..7397d0edf 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -456,6 +456,11 @@ sub www_loadSurvey { elsif ( $var->{type} eq 'answer' ) { $editHtml = $self->processTemplate( $var, $self->get("answerEditTemplateId") ); } + + # Generate the list of valid goto targets + my @section_vars = map {$_->{variable}} @{$self->survey->sections}; + my @question_vars = map {$_->{variable}} @{$self->survey->questions}; + my @gotoTargets = grep {$_ ne ''} (@section_vars, @question_vars); my %buttons; $buttons{question} = $$address[0]; @@ -509,7 +514,8 @@ sub www_loadSurvey { #type is the object type my $return = { "address", $address, "buttons", \%buttons, "edithtml", $editHtml, - "ddhtml", $html, "ids", \@ids, "type", $var->{type} + "ddhtml", $html, "ids", \@ids, "type", $var->{type}, + gotoTargets => \@gotoTargets, }; $self->session->http->setMimeType('application/json'); return to_json($return); diff --git a/survey_rfe_9202.wgpkg b/survey_rfe_9202.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..739bb1699779d4330a31cdb35061337b4e2b30e3 GIT binary patch literal 8874 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 literal 0 HcmV?d00001 diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index f1e4c0a7f..6679696da 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -77,11 +77,17 @@ YAHOO.log('adding handler for '+ d.ids[x]); // } if(d.buttons['answer']){ var button = new YAHOO.widget.Button({ label:"Add Answer", id:"addanswer", container:"addAnswer" }); - button.on("click", this.addAnswer,d.buttons['answer']); + button.on("click", this.addAnswer,d.buttons['answer']); } if(showEdit == 1){ this.loadObjectEdit(d.edithtml,d.type); + + // build the goto auto-complete widget + if (d.gotoTargets && document.getElementById('goto')) { + var ds = new YAHOO.util.LocalDataSource(d.gotoTargets); + var ac = new YAHOO.widget.AutoComplete('goto', 'goto-yui-ac-container', ds); + } }else{ document.getElementById('edit').innerHTML = ""; } diff --git a/www/extras/wobject/Survey/surveyedit.css b/www/extras/wobject/Survey/surveyedit.css index 0050e6c20..46d4fe0c3 100644 --- a/www/extras/wobject/Survey/surveyedit.css +++ b/www/extras/wobject/Survey/surveyedit.css @@ -129,4 +129,7 @@ li.newAnswer { padding-left:50px; # cursor: move; } - +#goto-yui-ac { + width:15em; + margin-top:0.5em; +} From ad0f3b388d0486aa5b00a6fcd9af7bc86ca13d3f Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 15 Dec 2008 10:25:22 +0000 Subject: [PATCH 03/90] SurveyJSON->update() should iterate over supplied object keys, not existing object keys. Also applied PBP formatting, fat commas, etc.. --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 114 ++++++++++-------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index ad126a066..dc024ff54 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -29,6 +29,7 @@ Asset in WebGUI. use strict; use JSON; + #use Clone qw/clone/; use Storable qw/dclone/; @@ -182,16 +183,16 @@ sub getDragDropList { for ( my $x = 0; $x <= $#{ $self->questions($address) }; $x++ ) { push( - @data, { - text => $self->question( [ $i, $x ] )->{text}, + @data, + { text => $self->question( [ $i, $x ] )->{text}, type => 'question' } ); if ( $address->[1] == $x ) { for ( my $y = 0; $y <= $#{ $self->answers($address) }; $y++ ) { push( - @data, { - text => $self->answer( [ $i, $x, $y ] )->{text}, + @data, + { text => $self->answer( [ $i, $x, $y ] )->{text}, type => 'answer' } ); @@ -245,7 +246,8 @@ sub getObject { return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; } else { - return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; + return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers} + ->[ $address->[2] ]; } } @@ -363,7 +365,7 @@ type to the Survey, you must handle it here, and also in updateQuestionAnswers =cut sub getValidQuestionTypes { - return( + return ( 'Agree/Disagree', 'Certainty', 'Concern', 'Confidence', 'Currency', 'Date', 'Date Range', 'Dual Slider - Range', 'Education', 'Effectiveness', 'Email', 'File Upload', @@ -474,7 +476,7 @@ sub update { $self->updateQuestionAnswers( $address, $ref->{questionType} ); } } - for my $key ( keys %$object ) { + for my $key ( keys %$ref ) { $object->{$key} = $ref->{$key} if ( defined $$ref{$key} ); } } ## end sub update @@ -646,18 +648,22 @@ Returns a reference to a new, empty section. =cut sub newSection { - my %members = ( - 'text', '', - 'title', 'NEW SECTION', ##i18n - 'variable', '', 'questionsPerPage', 5, - 'questionsOnSectionPage', 1, 'randomizeQuestions', 0, - 'everyPageTitle', 1, 'everyPageText', 1, - 'terminal', 0, 'terminalUrl', '', - 'goto', '', 'timeLimit', 0, - 'type', 'section' - ); - $members{questions} = []; - return \%members; + return { + text => '', + title => 'NEW SECTION', ##i18n + variable => '', + questionsPerPage => 5, + questionsOnSectionPage => 1, + randomizeQuestions => 0, + everyPageTitle => 1, + everyPageText => 1, + terminal => 0, + terminalUrl => '', + goto => '', + timeLimit => 0, + type => 'section', + questions => [], + }; } =head2 newQuestion @@ -667,28 +673,26 @@ Returns a reference to a new, empty question. =cut sub newQuestion { - my %members = ( - 'text', '', - 'variable', '', - 'allowComment', 0, - 'commentCols', 10, - 'commentRows', 5, - 'randomizeAnswers', 0, - 'questionType', 'Multiple Choice', - 'randomWords', '', - 'verticalDisplay', 0, - 'required', 0, - 'maxAnswers', 1, - 'value', 1, - 'textInButton', 0, - - # 'terminal',0, - # 'terminalUrl','', - 'type', 'question' - ); - $members{answers} = []; - return \%members; -} ## end sub newQuestion + return { + text => '', + variable => '', + allowComment => 0, + commentCols => 10, + commentRows => 5, + randomizeAnswers => 0, + questionType => 'Multiple Choice', + randomWords => '', + verticalDisplay => 0, + required => 0, + maxAnswers => 1, + value => 1, + textInButton => 0, +# terminal => 0, +# terminalUrl => '', + type => 'question', + answers => [], + }; +} =head2 newAnswer @@ -697,11 +701,22 @@ Returns a reference to a new, empty answer. =cut sub newAnswer { - my %members = ( - 'text', '', 'verbatim', 0, 'textCols', 10, 'textRows', 5, 'goto', '', 'recordedAnswer', '', 'isCorrect', 1, - 'min', 1, 'max', 10, 'step', 1, 'value', 1, 'terminal', 0, 'terminalUrl', '', 'type', 'answer' - ); - return \%members; + return { + text => '', + verbatim => 0, + textCols => 10, + textRows => 5, + goto => '', + recordedAnswer => '', + isCorrect => 1, + min => 1, + max => 10, + step => 1, + value => 1, + terminal => 0, + terminalUrl => '', + type => 'answer' + }; } =head2 updateQuestionAnswers ($address, $type); @@ -769,12 +784,13 @@ sub updateQuestionAnswers { $self->addAnswersToQuestion( \@addy, \@ans, { 7, 1 } ); } elsif ( $type eq 'Party' ) { - my @ans = ( 'Democratic party', 'Republican party (or GOP)', 'Independant party', 'Other party (verbatim)' ); + my @ans + = ( 'Democratic party', 'Republican party (or GOP)', 'Independant party', 'Other party (verbatim)' ); $self->addAnswersToQuestion( \@addy, \@ans, { 3, 1 } ); } elsif ( $type eq 'Race' ) { - my @ans - = ( 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', 'Something else (verbatim)' ); + my @ans = ( 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', + 'Something else (verbatim)' ); $self->addAnswersToQuestion( \@addy, \@ans, { 5, 1 } ); } elsif ( $type eq 'Ideology' ) { From fc3851705a4a9297c7c3d6fe3c7daf623e26624a Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 15 Dec 2008 10:25:44 +0000 Subject: [PATCH 04/90] Implemented Survey branching expressions (#9233). Woot! --- lib/WebGUI/Asset/Wobject/Survey.pm | 4 +- .../Asset/Wobject/Survey/ResponseJSON.pm | 179 +++++++++++++++++- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 9 + lib/WebGUI/i18n/English/Asset_Survey.pm | 4 + t/Asset/Wobject/Survey/ResponseJSON.t | 76 +++++++- t/Asset/Wobject/Survey/SurveyJSON.t | 1 + 6 files changed, 268 insertions(+), 5 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 7397d0edf..ae50df320 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -458,9 +458,7 @@ sub www_loadSurvey { } # Generate the list of valid goto targets - my @section_vars = map {$_->{variable}} @{$self->survey->sections}; - my @question_vars = map {$_->{variable}} @{$self->survey->questions}; - my @gotoTargets = grep {$_ ne ''} (@section_vars, @question_vars); + my @gotoTargets = $self->survey->getGotoTargets; my %buttons; $buttons{question} = $$address[0]; diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index c939d2809..9210235c1 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -400,6 +400,7 @@ sub recordResponses { my $terminal = 0; my $terminalUrl; my $goto; + my $gotoExpression; my $section = $self->nextSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions @@ -444,6 +445,9 @@ sub recordResponses { elsif ( $answer->{goto} =~ /\w/ ) { $goto = $answer->{goto}; } + elsif ( $answer->{gotoExpression} =~ /\w/ ) { + $gotoExpression = $answer->{gotoExpression}; + } } ## end if ( defined( $responses... } ## end for my $answer ( @{ $question... $qAnswered = 0 if ( !$aAnswered and $question->{required} ); @@ -456,6 +460,7 @@ sub recordResponses { if ($qAnswered) { $self->lastResponse( $self->lastResponse + @$questions ); $self->goto($goto) if ( defined $goto ); + $self->gotoExpression($gotoExpression) if ( defined $gotoExpression ); } else { $terminal = 0; @@ -501,6 +506,164 @@ sub goto { #------------------------------------------------------------------- +=head2 gotoExpression ( $gotoExpression ) + +=head3 $gotoExpression + +The gotoExpression (one expression per line) + +=head3 Explanation + +A gotoExpression is a list of expressions (one per line) of the form: + target: expression + target: expression + +This subroutine iterates through the list, processing each line and, all things being +well, evaluates the expression. The first expression to evaluate to true triggers a +call to goto($target). + +The expression is a simple subset of the formula language used in spreadsheet programs such as Excel, OpenOffice, Google Docs etc.. + +Here is an example using section variables S1 and S2 as jump targets and question variables Q1-3 in the expression. +It jumps to S1 if the user's answer to Q1 has a value of 3, jumps to S2 if Q2 + Q3 < 10, and otherwise doesn't branch at all (the default). +S1: Q1 = 3 +S2: Q2 + Q3 < 10 + +=head3 Arguments are evaluated as follows: + +Numeric arguments evaluate as numbers +* No support for strings (and hence no string matching) +* Question variable names (e.g. Q1) evaluate to the numeric value associated with user's answer to that question, or undefined if the user has not answered that question + +Binary comparisons operators: = != < <= >= > +* return boolean values based on perl's equivalent numeric comparison operators + +Simple math operators: + - * / +* return numeric values + +Later we may add Boolean operators: AND( x; y; z; ... ), OR( x; y; z; ... ), NOT( x ) +* args separated by semicolons (presumably because spreadsheet formulas use commas to indicate cell ranges) + +Later still you may be able to say AVG(section1) or SUM(section3) and have those functions automatically compute their result over the set of all questions in the given section. +But for now those things can be done manually using the limited subset defined. + +=cut + +sub gotoExpression { + my $self = shift; + my $expression = shift; + + my %responses = ( + # questionName => response answer value + ); + + # Populate %responses with the user's data.. + foreach my $q (@{ $self->returnResponseForReporting() }) { + if ($q->{questionName} =~ /\w/) { + my $value = $q->{answers}[0]{value}; + $responses{$q->{questionName}} = $value if defined $value; + } + } + + # Process gotoExpressions one after the other (first one that's true wins) + foreach my $line (split '\n', $expression) { + my $processed = $self->processGotoExpression($line, \%responses); + + next unless $processed; + + # (ab)use perl's eval to evaluate the processed expression + my $result = eval "$processed->{expression}"; + $self->warn($@) if $@; + + if ($result) { + $self->debug("Truthy, goto [$processed->{target}]"); + $self->goto($processed->{target}); + return $processed; + } else { + $self->debug("Falsy, not branching"); + next; + } + } + return; +} + +=head2 processGotoExpression ( $expression, $responses) + +Parses a single gotoExpression. Returns undef if processing fails, or the following hashref +if things work out well: + { target => $target, expression => $expression } + +=head3 $expression + +The expression to process + +=head3 $responses + +Hashref that maps questionNames to response values + +=head3 Explanation: + +Uses the following simple strategy: + +First, parse the expression as: + target: expression + +Replace each questionName with its response value (from the $responses hashref) + +Massage the expression into valid perl + +Check that only valid tokens remain. This last step ensures that any invalid questionNames in +the expression generate an error because our list of valid tokens doesn't include a-z + +=cut + +sub processGotoExpression { + my $self = shift; + my $expression = shift; + my $responses = shift; + + $self->debug("Processing gotoExpression: $expression"); + + # Valid gotoExpression tokens are.. + my $tokens = qr{\s|[-0-9=!<>+*/.()]}; + + my ( $target, $rest ) = $expression =~ /\s* ([^:]+?) \s* : \s* (.*)/x; + + $self->debug("Parsed as Target: [$target], Expression: [$rest]"); + + if ( !defined $target ) { + $self->warn('Target undefined'); + return; + } + + if ( !defined $rest || $rest eq '' ) { + $self->warn('Expression undefined'); + return; + } + + # Replace each questionName with its response value + while ( my ( $questionName, $response ) = each %$responses ) { + $rest =~ s/$questionName/$response/g; + } + + # convert '=' to '==' but don't touch '!=', '<=' or '>=' + $rest =~ s/(?])=(?!=)/==/g; + + if ( $rest !~ /^$tokens+$/ ) { + $self->warn("Contains invalid tokens: $rest"); + return; + } + + $self->debug("Processed as: $rest"); + + return { + target => $target, + expression => $rest, + }; +} + +#------------------------------------------------------------------- + =head2 getPreviousAnswer =cut @@ -691,9 +854,23 @@ Logs an error to the webgui log file, using the session logger. =cut sub log { - my ( $self, $message ) = @_; + my ( $self, $message) = @_; if ( defined $self->{log} ) { $self->{log}->error($message); } } + +sub debug { + my ( $self, $message) = @_; + if ( defined $self->{log} ) { + $self->{log}->debug($message); + } +} + +sub warn { + my ( $self, $message) = @_; + if ( defined $self->{log} ) { + $self->{log}->warn($message); + } +} 1; diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index dc024ff54..4cbf78029 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -318,6 +318,14 @@ sub getSectionEditVars { return \%var; } ## end sub getSectionEditVars +sub getGotoTargets { + my $self = shift; + + my @section_vars = map {$_->{variable}} @{$self->sections}; + my @question_vars = map {$_->{variable}} @{$self->questions}; + return grep {$_ ne ''} (@section_vars, @question_vars); +} + =head2 getQuestionEditVars ( $address ) Get a safe copy of the variables for this question, to use for editing purposes. Adds @@ -707,6 +715,7 @@ sub newAnswer { textCols => 10, textRows => 5, goto => '', + gotoExpression => '', recordedAnswer => '', isCorrect => 1, min => 1, diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index af3180db9..c1480265f 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -195,6 +195,10 @@ our $I18N = { message => q|Jump to:|, lastUpdated => 1224686319 }, + 'jump expression' => { + message => q|Jump expression:|, + lastUpdated => 1229318805 + }, 'text answer' => { message => q|Text answer|, lastUpdated => 1224686319 diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index f8f424d52..a75f5b47c 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -20,7 +20,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 52; +my $tests = 77; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -319,6 +319,80 @@ is($rJSON->lastResponse(), 0, 'goto: works on existing question'); $rJSON->goto('goto 3-0'); is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); +#################################################### +# +# processGotoExpression +# +#################################################### +is($rJSON->processGotoExpression(), + undef, 'processGotoExpression undef with empty arguments'); +is($rJSON->processGotoExpression('blah-dee-blah-blah'), + undef, '.. and undef with duff expression'); +is($rJSON->processGotoExpression(':'), + undef, '.. and undef with missing target'); +is($rJSON->processGotoExpression('t1:'), + undef, '.. and undef with missing expression'); +cmp_deeply($rJSON->processGotoExpression('t1: 1'), + { target => 't1', expression => '1'}, 'works for simple numeric expression'); +cmp_deeply($rJSON->processGotoExpression('t1: 1 - 23 + 456 * (78 / 9.0)'), + { target => 't1', expression => '1 - 23 + 456 * (78 / 9.0)'}, 'works for expression using all algebraic tokens'); +is($rJSON->processGotoExpression('t1: 1 + &'), undef, '.. but disallows expression containing non-whitelisted token'); +cmp_deeply($rJSON->processGotoExpression('t1: 1 = 3'), + { target => 't1', expression => '1 == 3'}, 'converts single = to =='); +cmp_deeply($rJSON->processGotoExpression('t1: 1 != 3 <= 4 >= 5'), + { target => 't1', expression => '1 != 3 <= 4 >= 5'}, q{..but doesn't mess with other ops containing =}); +cmp_deeply($rJSON->processGotoExpression('t1: q1 + q2 * q3 - 4', { q1 => 11, q2 => 22, q3 => 33}), + { target => 't1', expression => '11 + 22 * 33 - 4'}, 'substitues q for value'); +cmp_deeply($rJSON->processGotoExpression('t1: a silly var name * 10 + another var name', { 'a silly var name' => 345, 'another var name' => 456}), + { target => 't1', expression => '345 * 10 + 456'}, '..it even works for vars with spaces in their names'); +is($rJSON->processGotoExpression('t1: qX + 3', { q1 => '7'}), + undef, q{..but doesn't like invalid var names}); + +#################################################### +# +# gotoExpression +# +#################################################### + +$rJSON->survey->section([0])->{variable} = 's0'; +$rJSON->survey->section([2])->{variable} = 's2'; +$rJSON->survey->question([1,0])->{variable} = 's1q0'; +$rJSON->survey->answer([1,0,0])->{value} = 3; + +$rJSON->lastResponse(2); +$rJSON->recordResponses($session, { + '1-0comment' => 'Section 1, question 0 comment', + '1-0-0' => 'First answer', + '1-0-0comment' => 'Section 1, question 0, answer 0 comment', +}); +is($rJSON->gotoExpression('blah-dee-blah-blah'), undef, 'invalid gotoExpression is false'); +ok($rJSON->gotoExpression('s0: s1q0 = 3'), '3 == 3 is true'); +ok(!$rJSON->gotoExpression('s0: s1q0 = 4'), '3 == 4 is false'); +ok($rJSON->gotoExpression('s0: s1q0 != 2'), '3 != 2 is true'); +ok(!$rJSON->gotoExpression('s0: s1q0 != 3'), '3 != 3 is false'); +ok($rJSON->gotoExpression('s0: s1q0 > 2'), '3 > 2 is true'); +ok($rJSON->gotoExpression('s0: s1q0 < 4'), '3 < 2 is true'); +ok(!$rJSON->gotoExpression('s0: s1q0 >= 4'), '3 >= 4 is false'); +ok(!$rJSON->gotoExpression('s0: s1q0 <= 2'), '3 >= 4 is false'); + +cmp_deeply($rJSON->gotoExpression(<<"END_EXPRESSION"), {target => 's2', expression => '3 == 3'}, 'first true expression wins'); +s0: s1q0 <= 2 +s2: s1q0 = 3 +END_EXPRESSION + +ok(!$rJSON->gotoExpression(<<"END_EXPRESSION"), 'but multiple false expressions still false'); +s0: s1q0 <= 2 +s2: s1q0 = 345 +END_EXPRESSION + +$rJSON->gotoExpression('s0: s1q0 = 3'); +is($rJSON->lastResponse(), -1, '.. lastResponse changed to -1 due to goto(s0)'); +$rJSON->gotoExpression('s2: s1q0 = 3'); +is($rJSON->lastResponse(), 4, '.. lastResponse changed to 4 due to goto(s2)'); + +$rJSON->{responses} = {}; +$rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); + #################################################### # # recordResponses diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index d308388b2..7208ea8c8 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -2118,6 +2118,7 @@ sub getBareSkeletons { textCols => 10, textRows => 5, goto => '', + gotoExpression => '', recordedAnswer => '', isCorrect => 1, min => 1, From e505238fc7d7f65af718a8f38f1261d8cc3b4755 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 17 Dec 2008 03:17:50 +0000 Subject: [PATCH 05/90] Nuked console.log() so that js doesn't break in non-FFx browsers --- www/extras/wobject/Survey/editsurvey.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index 6679696da..a1fbcbebf 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -53,7 +53,6 @@ YAHOO.log(first.id+' '+data.id); }else{ lastId = d.address; } -console.log(d.address); document.getElementById('sections').innerHTML=d.ddhtml; //add event handlers for if a tag is clicked From 7539246e9dbfdcff42946277a22e5ee1a2450c7a Mon Sep 17 00:00:00 2001 From: Yung Han Khoe Date: Thu, 18 Dec 2008 12:33:20 +0000 Subject: [PATCH 06/90] implemented Survey final page detection (#9199) --- lib/WebGUI/Asset/Wobject/Survey.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index ae50df320..7be6c74f3 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -826,6 +826,10 @@ sub prepareShowSurveyTemplate { $section->{'showTimeLimit'} = $self->get('showTimeLimit'); $section->{'minutesLeft'} = int((($self->response->startTime() + (60 * $self->get('timeLimit'))) - time())/60); + if(scalar @$questions == ($section->{'totalQuestions'} - $section->{'questionsAnswered'})){ + $section->{isLastPage} = 1 + } + my $out = $self->processTemplate( $section, $self->get("surveyQuestionsId") ); $self->session->http->setMimeType('application/json'); From 3889c937bf373ed0eb69e67ad53eea77e9be9460 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 18 Dec 2008 22:18:11 +0000 Subject: [PATCH 07/90] Added Yung's package for RFE #9199 --- rfe9199-survey-default-questions.wgpkg | Bin 0 -> 2549 bytes survey_rfe_9202.wgpkg => rfe9202-survey.wgpkg | Bin 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 rfe9199-survey-default-questions.wgpkg rename survey_rfe_9202.wgpkg => rfe9202-survey.wgpkg (100%) diff --git a/rfe9199-survey-default-questions.wgpkg b/rfe9199-survey-default-questions.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..22e638b7c5955987ad4abfb5650c65b0910207b9 GIT binary patch literal 2549 zcmVExUL9@3*@E2=FPzv2$}X;fyU3kA3fA0qnEAr+@nW{%APd+w1oSqv2q0 z?_eY!Cx7~TgTY{b|L|}y8XfI9gZ^N6xaYu>r++e$dI^xWVY6k%AC3>0ga?_FzYmA> zRc8{l1M~mjnBGUvK8h%OBuW6{(cxi}{`U_?N5hi-4-QBDJqM)u^v_f2|Jl=%{umhk z{T(=AN2F2WEm_AoasK(Q@2M$HqL?KqX98Pk7>=AL%uAX7;vQQ&2OS!W9f2p(S9fug zK<^pN39}%`yTQv+g5Qa=S-{1L`DSj&HXnHCM;ij#Hyz?WjAFs7;G#{)lJ|lo7k-%# zc9$mJ8|L{p-m*H;6N05`()r!n_|>oPuf6OaX9w}~kACv=*^7p0M|8~gm$;r{8gBkAa_fKQf8JCkWL4Rtuxqhx2w(|}FJDS-L))=QkwTeDM_ zTHVRG2N2sPgEXsiq0yddfJiKSA&~%;t7rpUFB2w&B66B)@}?cIv0@i=mgvj{d=`og zOPKFYoG443XyKHHf^5CuFIpikaRmT#4#z!$%@@>DN9qk4RvUcGf)1QYJ3h z0wFl+(+Th!^wUC&(yN=ZtH_60<3*ILmDZ%zUqgfIfYD_$pcRZEVQH3x&cX`>`$5x= zQ;Fdqiefdt1Rf2Bv@r+o6{k6NiF_PS#v=AYG&r{`Yz~S@*l!spm5h5hu+dNeP_6}S zsQ6pdfx8Be;&H&vR}r5xNknebN_UEpwRiV6O4!e#8aDn@x2uP0t|o#7Y);-uDmZc4 z*#Vef)g}9bMrnCMTYR@$^p}EF5$-|@DIR#m9?g4+?*$LoWrU7g$FrqAk9+_q(SQ&r zK4q$JA#&FZBXvq;OIQ_11dWF=xEMyNQx}PImDEdUrXB8swYhZ$!hL8L{C9vb?7L2{ zf=HQ6JtsPXxWLHFOZmFWG7(Bv=Zr0SQ;1Cx@31eac4}=m2kd}t5||1lxf+e)5Gg(- zK_HoF*6XC2LsG>R}j`-8)%fq!y0p_Z=_b z%Mc7|&cYNkKgZc{KV6-UhVfnNWJ8c`?OfMhz+RoTUi*V#>^;pEp9Jt`t^G$ypd(KThtCz;EpA?RTEvbhilyBUm(Ag zJw4u*J$gc(HZ}@PRI~lqc)wPpMf@dY?j9d#vfNf%i`Ekas)qfKEj*%Shj66EHOJovIcB|Lp%v4% z+!TtV@b72dVY|fL23S9wH#oKq;AVuCD?LI+$o$cU5B9 zxUHbIW5+~+j({eLB|4vga{@Z)AkS)M(l~B|zZm=b;`0I&jn;+%i!FI;k!KO`hSQ^5 zEPmz%@2p`Ol2(IMtl6u$-k^Q~t_h?~ifcGGQl=r()qcew6vr*8i%sSJSD~`HSE19w znoPS)eMQb>>aAyRc<@>X1yvD|`fV{ZDXZ17@Oa*x^xF-(UJoh&Vf>*;v;&Dvn%aTH za@3JWwXNrNEo9e)Ocit|{Xd5@5&t0nH#653Sfw*|XG@5F-VE-w*xjky9l#gf#GJay z3)RLsAH9PFo0Pl*HELY?SX}jeg^kUY5y)10xIOWUapT~iykXc<>{*z1kvU^TCd^Q+hlxtFd>a6svj8;(5Wz0%~EX;KW!f&Z_$}H7q<_v4p4Zli+m{t*=2bp)7Q;EZUwPrOsdVXWOleqyEA3{pSWFC2!RGQr7-Bd8vsRqzZZd%9$)>8ksIGu)GHyP@*tB;uDXrq7=~2 z;i@~5g+rEPz~r+VOdYpj#~Q1QN}f7i47D}4c8wJkKD~nrd4lr%Xmn&Kh`j_#si>nt zlqV-|Zr)y21q*f;%|B_O7An5DP=X@37C%%9rj$W_$H$}N&l? zO1QZ8=AX3DSfy6Duek`ZOh!t>>cR~^tgLe>29>Sec%PV&1`FrOeN4_&CO!3*s&=e< z`{Vj}nH}DIIQuC+T>SmT%`aj9%mCry-MxU?tuh2+Ov}rzp9A}#>bW`#p`yEzG@zpP zXn0UmBDj?zZ#WCDeoxueXmfBC{&c*4efT$izU*LS-yh%o@fd}J-(Y-`z&8o}-%H@X L|Aase05AXmFstjm literal 0 HcmV?d00001 diff --git a/survey_rfe_9202.wgpkg b/rfe9202-survey.wgpkg similarity index 100% rename from survey_rfe_9202.wgpkg rename to rfe9202-survey.wgpkg From c47a6fb466774b807ddeda8f13554fcaf52f064e Mon Sep 17 00:00:00 2001 From: Yung Han Khoe Date: Tue, 6 Jan 2009 13:23:52 +0000 Subject: [PATCH 08/90] rfe 9198: Turn Survey Edit Section/Question/Answer fields into RTEs --- ...ot_import_survey_default-answer-edit.wgpkg | Bin 0 -> 1401 bytes ..._import_survey_default-question-edit.wgpkg | Bin 0 -> 1598 bytes ...t_import_survey_default-section-edit.wgpkg | Bin 0 -> 1614 bytes .../wobject/Survey/editsurvey/object.js | 27 +++++++++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 rfe-9198-root_import_survey_default-answer-edit.wgpkg create mode 100644 rfe-9198-root_import_survey_default-question-edit.wgpkg create mode 100644 rfe-9198-root_import_survey_default-section-edit.wgpkg diff --git a/rfe-9198-root_import_survey_default-answer-edit.wgpkg b/rfe-9198-root_import_survey_default-answer-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..5e32dd3a32ca1405c2a7afa0b51955a758ea84b2 GIT binary patch literal 1401 zcmV-<1%~<`iwFP!000001MOE$bK5o$)j7X{qYK-aBqH@0DUv#_n>3y#vL|+uPU|)U zkt-242`~UCS?T1zcL9(hE!s}vvD=GzuqlAM`}XYuczD{l|Ht#ZL9f?nc%DD#`He|4ucn#n8J6^xn>-YN&$M^ic*Kp9}{+~=LA`sdd=F5!UJ{~5Ck51D5zrCmb)Y72! z%fFqWe)qknXOgo!LV^za-R`yZA9z^v%k}ScyX}U9Y~KHKU-f@_|IqIu!at925jLgb zf-IrwjGRCJdTfSL!BY^5g4CntGyP>CfG7xm-ssV0r)fUc6ETtM#YM^m&h8l-mc{XQ z8dsSz{+nqwkEx6xENvOAKTsC(wQ=f|6LQ0NDrwyvUo#NzB#47>7YJM^L0$m~Psp-v zNTdXn8||G%@$9g({$n-kop)*X6XhR*Wf?sav2iXqS1p?8+*&ddE7-I`Sdc7MPY9E1 z5Kkbas6%9jR#@Mfp9|^9w+N73=N}@}_v-BW*q5WuL{dQ@lAW7qby>Uoa4h<)+ zjqFZO#VIp`0aidx+$eOXe;+Uff)PbIBk>Pz8UvBwfF5$zJq^}r<5Q75O6rgyw&b%!%H)o`3+w;_3}&}e-CrJDgIfOq>kcO#I00PCsME75|`98udaS~B>fDNZW&FcgqezI z`y(PqA`x#|f$SR3mPL13X)ZV5vHo}$gbC5EW6^&0(WJn${ay7b_!@ocHraXfy3#}6 z749v1tA;bbopB5EYw65O9_m%6(ovE50}j?7E2pk)fCAhuKv$5E^6l!9`_@mf$oxEW zx^QSQHw_{P&LPCs)T@{-rnU9I0a@>bTSD-12pNk(%4=cR={gj4MOPL`uGqN}%Ie5# z6|L<+XjW7aUu_S!Z>n8FE^ZZvTx{czi*JY{6{NQbr0t4G`u!V1tw7B2KuYT23chb{ zzO8+}Fe|-ROw}4KZ^e4Z`)SSsN3lG1db61RZkTt}y!o+fy7LN3>^6v<7Xw?WXySXe zs(pLXnq9Qk*=C^V1p7npPP(MymX;3r>sE0X?NQ1f$|4X{y%X_E!4urSQN|dJRFO|H zWsvLFW_g#vYYaZcji&Tyl+p~Pi*fS-=D)r>7>$es8jVg0wRKs#?ni_zuhYG6ct{r% z6|I$S9lzZj;0?BnsHloW1?Pg@|5_v()+;BJ;4ts-kg>eBZDp7&{QV3_iT`vojM#McMxB{rvJqcWu>FP^J`rNJuZ*5xt+3&bWxsbOza%?d0OH-mq&fs z-Cy?(pF9f|arAm!QcC6V2KUWmH+hp-!B_PHh8<$AUsv^rxwTzpquuwsVZZmRjEF_V zrIo|$&Drd;g4tl*nX%u7$;@|H;6=kGOqB H02lxO<`}{+ literal 0 HcmV?d00001 diff --git a/rfe-9198-root_import_survey_default-question-edit.wgpkg b/rfe-9198-root_import_survey_default-question-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..19086d8a6782934ee20061859f5daa03b7fa61ac GIT binary patch literal 1598 zcmV-E2Eq9siwFP!000001MOH*Z`(Ey_H%y);fHAh+D3})7Jmz9S`CidnmDVDDjw9uk@4-F^J-NZsAj+V&sc_uH*jt>*hdyA{-G zjduQ^#pBn4AZYk|tybIjYbfx8W~+ukWcyDh6&3((Mn>a|-rpW<5+1$D{Qvzm{il)! zZBYJgbj*9_>6PScLr6g5UbDH%|KRx6-je?t%~rF9fX(ec+w%X@?L*&$g?}EwA*@Tq z1s;<+>Y_jYdTfSL!BZj>B~p)?WqM^O2v#I|KG&o5M%^sd6ET+R3%G80oW--*a2&SLfC zQ%0nsoIN2CRiz}AuW1w!X2gT)__5%Mt9hDpQQ7KP@GL!yUTQs>+F-X7QzS`a%U>^| zQ$(Y_XKj0fb8*hha0p(IeQy$ZgTD`%B7$L!4#z)wX-u#ri0DydMG<9}TqI_Z_t@Ll z+OIkNBIbpYlHgo?rCYna6 zY?M&-eso1brT1CUlR9`85LuRth;Y%No&r`brdXheO5kTsq6-?8*eFXgg*4Z|h;fHk zK&6p~rZ~=EqErH+6+o+nBN7uuJmUiUo-6ae#dgTkc`ls4IihQ*P!QWL5esMNX$d3{h4qxB4WJ63zS z$u&s9D-@4nVxYK?dpW%czKIDNt4Y7nLXzH+e&FX0)5csES*?S@k@j~ky9*%Gb?5|! zJ0<}$2s1e&Lb|{#mnOKYF(7b6xs9KNqWwZo0W~OMl3t?aB?A;rNO(;m1Wf9utC&vm$WOm%wOL|1KD$1ky5UE;vm zAi+3}f#HxR2_!T3rPe4pD0H~gR;Qi4Z20P~+FC(vb%_J#d#N>tT&9?r6sF+u7z><8 zZvX-P0~^WoS^gIt@K|<>&$UUVw3mzTt$W=T%CwOEMpuW>?|de^_UvdJ`uSe%TFH`j zGX!7O6jA`avvs#2Bpo!#AZs;wCE}N>vjGR0lQe>|A_uUD$`PBD*!_Ott`Ou?Mg@uP z8^sHGH-hby6D}K$UTbGX>^X7E^HN*>nSC9{a(>QrCzEFf!s`~U0&1lh%0~)n(A|z9k|=G;kP2t z&4tX=`gNQ_<6>HEE|`k{1hRf(qR|d2_);vOhhQdFP}=Rj9=?0)5=`Y=uG9Iv5pt*1 zL1jVM@}5i&q;QIv>vYTmr_x{0xyK=U z1p%6~QJK<3I$VumPD5Vf@7#4HVWF6(>ewo-z*2ZzG)*o#=_$E|)=9wLT+gmPzH6La zzpXS)s63fVXjm+7-fYbBRc#zWjF`63m!iaUo9?o)yXX6zy;cPqI1!Fl0k1V;N4JU` wwP%eZ_NJ4(YJQ{#@|AT>l0n_*JfB+l-02mD@{{R30 literal 0 HcmV?d00001 diff --git a/rfe-9198-root_import_survey_default-section-edit.wgpkg b/rfe-9198-root_import_survey_default-section-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..09c5c616b8afca71bd07244d7b62d84dee063781 GIT binary patch literal 1614 zcmV-U2C?}ciwFP!000001MOH_Z`(E)^>cp(;TLT{Q%k$&+JiMJ}wZlKQZTC8zTFthdUdO4`TD|l^ z`D51{$7$K!PN!%0YRIvjPP>M{g)HPo_!J#+fydsvDt~uOcKtpN)87SAT}M3BeXOU}u|nqR zT-Rs)_P7GrE<3!tAX%HKqN7a%&*j$U6ga1xf{hRW1PNMOm`pw z=x@Uz4VI3Lq%wf?D%I)lV=IM2!n7%J^?C7L4bCyl)sTK6=--40MKMv$L9xUzMYE+` zKgS-8OwqZ!-*RtRvQ9Rj^cqz=jgjjU_cQU(u%U;YUXLq`S&>^(Qz@xu_zh{S+qQ`_cv*ob!##N;@GQFDDXhc)j&i7MA7NgYNPcV~h*ZxQTqD2A=C3Y{VE zP{90lWwuV_mS=>YDZ3m8%_dF6Ad2E*YGfVP`suz=tfo#CeAo%e4HT8GL%{ts0pg9# z%g8BFzO-Cv!(k(JycRI)uQJQO4;!gJ{Q)|7cP+np?FiB~W4s+aAa0IdHaQDKPT6_7 zZ7Klse!FL59(&E$tZEL*7lVB+0lS0Rl~A`$=Ykp}5PPb)68t-sx;yIJ0q)L<^JO`w zLbk8uzpKle-jo!-$b#_IT~pIgOLtamx=?%FJt?(rbYLoN4|ZS*Z4c_~lRDc&*!!c* z7K*@8{ z8>ah#39%XGz+Bi2w^tf$I)6|GAh^a?S+I8SSC^mkd}lI!&x7j0tkjs@3)53mqjZU_ zE?4HDq=HoQP$y}m_Cxh=sQO?~ShB;WHbFwJ0y4U+FR3T}LAzteH_v@S=e`_(a0fpw z>qsdAm-Q^v1$eov!)eZ3hLs;Uvm5W4=%xF8e$!Vu6tV+lKH*dX6em0i&mb?BY7-^d zBA|@GwyPzJLPWH+plGDT!lM=_rf_A|{XdY|FYhOVffgPHgA1c_tZPOueaz;!2~O`K zk6u%dl2XZ}ZR zvT~Z)z;p`is5ry!XG5KmMkf4dIbkQDC#7hVn$%TP#Mn(?NR_I_`ZyBi@>ML=QE)SL ziRYQ_H|MM4kC^@WE+AJjo=ra-`2jEKepH;V1c+{%9&}*_ud?;1PjG1OS2m M0n=}?CjcG*0LHf?W&i*H literal 0 HcmV?d00001 diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index cf67d07d1..7d9b9d583 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -8,8 +8,15 @@ Survey.ObjectTemplate = new function(){ document.getElementById('edit').innerHTML = html; + var myTextarea; + + var handleSubmit = function(){ + myTextarea.saveHTML(); + this.submit(); + } + var butts = [ - { text:"Submit", handler:function(){this.submit();}, isDefault:true }, + { text:"Submit", handler:handleSubmit, isDefault:true }, { text:"Copy", handler:function(){document.getElementById('copy').value = 1; this.submit();}}, { text:"Cancel", handler:function(){this.cancel();}}, { text:"Delete", handler:function(){document.getElementById('delete').value = 1; this.submit();}} @@ -26,6 +33,24 @@ Survey.ObjectTemplate = new function(){ form.callback = Survey.Comm.callback; form.render(); + + var textareaId = type+'Text'; + var textarea = YAHOO.util.Dom.get(textareaId); + var height = YAHOO.util.Dom.getStyle(textarea,'height'); + if (height == ''){ + height = '300px'; + } + var width = YAHOO.util.Dom.getStyle(textarea,'width'); + if (width == ''){ + width = '500px'; + } + myTextarea = new YAHOO.widget.SimpleEditor(textareaId, { + height: height, + width: width, + dompath: false //Turns on the bar at the bottom + }); + myTextarea.render(); + form.show(); } }(); From 1823bdefe3596cd70cae62e3325ec6dcd9d3cc97 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 8 Jan 2009 03:14:47 +0000 Subject: [PATCH 09/90] Deleted unused js files --- .../wobject/Survey/editsurvey/answer.js | 69 ----------- .../wobject/Survey/editsurvey/question.html | 40 ------- .../wobject/Survey/editsurvey/question.js | 112 ------------------ .../wobject/Survey/editsurvey/section.js | 28 ----- 4 files changed, 249 deletions(-) delete mode 100644 www/extras/wobject/Survey/editsurvey/answer.js delete mode 100644 www/extras/wobject/Survey/editsurvey/question.html delete mode 100644 www/extras/wobject/Survey/editsurvey/question.js delete mode 100644 www/extras/wobject/Survey/editsurvey/section.js diff --git a/www/extras/wobject/Survey/editsurvey/answer.js b/www/extras/wobject/Survey/editsurvey/answer.js deleted file mode 100644 index f9fdabac7..000000000 --- a/www/extras/wobject/Survey/editsurvey/answer.js +++ /dev/null @@ -1,69 +0,0 @@ -if (typeof Survey == "undefined") { - var Survey = {}; -} - -Survey.AnswerTemplate = new function(){ - this.params; - this.loadAnswer = function(params){ - for(var p in params){ - if(params[p] == undefined){params[p] = '';} - } - - var html = "\ -
\ -
Please enter answer information
\ -
\ -\ -
\ -\ -

Answer Number: "+params.sequenceNumber + "\ -\ - \ - \ - "; - html = html + "

Answer Text:\n\n"; - html = html + "

Recorded Answer\n\n"; - html = html + "

Jump to:"; - html = html + "

Text Answer Cols: Rows: \ -

"; - html = html + "

Is this the correct answer:\n" + - this.makeRadio('isCorrect',[{text:'Yes',value:1},{text:'No',value:0}],params.isCorrect); - html = html + "

Min:"; - html = html + "

Max:"; - html = html + "

Step:"; - html = html + "

Verbatim:\n" + - this.makeRadio('verbatim',[{text:'Yes',value:1},{text:'No',value:0}],params.verbatim); - document.getElementById('edit').innerHTML = html; - - var butts = [{ text:"Submit", handler:function(){this.submit();}, isDefault:true },{ text:"Cancel", handler:function(){this.cancel();}} ]; - if(params.Survey_answerId != ''){ - butts[2] = { text:"Delete", handler:function(){Survey.Comm.deleteAnswer(Survey.AnswerTemplate.params.Survey_answerId);}}; - } - - var form = new YAHOO.widget.Dialog("answer", - { width : "500px", - fixedcenter : true, - visible : false, - constraintoviewport : true, - buttons : butts - }); - - form.callback = Survey.Comm.callback; - form.render(); - form.show(); - this.params = params; - }; - - this.makeRadio = function(name,values,checked){ - var html = ''; - for(var i in values){ - if(checked == values[i]['value']){ - html = html+ "" + values[i]['text']; - }else{ - html = html+ "" + values[i]['text']; - } - } - html = html + "\n"; - return html; - } -}(); diff --git a/www/extras/wobject/Survey/editsurvey/question.html b/www/extras/wobject/Survey/editsurvey/question.html deleted file mode 100644 index e63ace0ea..000000000 --- a/www/extras/wobject/Survey/editsurvey/question.html +++ /dev/null @@ -1,40 +0,0 @@ -

-
Please enter question information
-
- -

Question Number: - -

Question Text:\n"; - \n"; -

Question variable name:

"; -

Randomize answers:"; - checked>Yes - checked>No -

Question type: - -

Randomized words: - -

Vertical display: - checked>Yes - checked>No - -

Show text in button: - checked>Yes - checked>No - -

Allow comment: - checked>Yes - checked>No -

   Cols: Rows: -

-

Maximum number of answers: -

Required: - checked>Yes - checked>No -

-
-
diff --git a/www/extras/wobject/Survey/editsurvey/question.js b/www/extras/wobject/Survey/editsurvey/question.js deleted file mode 100644 index 214c4b28b..000000000 --- a/www/extras/wobject/Survey/editsurvey/question.js +++ /dev/null @@ -1,112 +0,0 @@ -if (typeof Survey == "undefined") { - var Survey = {}; -} - -Survey.QuestionTemplate = new function(){ - - this.loadQuestion = function(params){ - - for(var p in params){ - if(params[p] == undefined){params[p] = '';} - } - - var html = "\ -
\ -
Please enter question information
\ -
\ -\ -
\ -

Question Number: "+params.sequenceNumber + "\ -\ - \ - \ -

Question Text:\n"; - if(params.questionText == ''){ - html = html + "\n"; - } - else{ - html = html + "\n"; - } - html = html + "

Question variable name:

"; - html = html + "

Randomize answers:"; - - html = html+ this.makeRadio('randomizeAnswers',[{text:'Yes',value:1},{text:'No',value:0}],params.randomizeAnswers); - html = html + "

Question type:"; - var questions = ['Agree/Disagree','Certainty','Concern','Confidence','Currency','Date','Date Range','Dual Slider - Range','Education','Effectiveness', - 'Email','File Upload','Gender','Hidden','Ideology','Importance','Likelihood','Multi Slider - Allocate','Multiple Choice','Oppose/Support', - 'Party','Phone Number','Race','Risk','Satisfaction','Scale','Security','Slider','Text','Text Date','Threat','True/False','Yes/No']; -// var questions = ['Multiple Choice','Gender','Yes/No','True/False','Agree/Disagree','Oppose/Support','Importance','Likelihood','Certainty','Satisfaction', -// 'Confidence','Effectiveness','Concern','Risk','Threat','Security','Ideology','Race','Party','Education', -// 'Text', 'Email', 'Phone Number', 'Text Date', 'Currency', -// 'Slider','Dual Slider - Range','Multi Slider - Allocate', 'Date','Date Range', 'File Upload','Hidden']; - - html = html + this.makeMenu('questionType',questions,questions,params.questionType); - - html = html + "\ -

Randomized words:\ - \ -

Vertical display:"; - - html = html+ this.makeRadio('verticalDisplay',[{text:'Yes',value:1},{text:'No',value:0}],params.verticalDisplay); - html = html + "

Show text in button:"; - html = html + this.makeRadio('textInButton',[{text:'Yes',value:1},{text:'No',value:0}],params.textInButton); - html = html + "

Allow comment:"; - html = html + this.makeRadio('allowComment',[{text:'Yes',value:1},{text:'No',value:0}],params.allowComment); - html = html + "

   Cols: Rows: \ -

"; - html = html + "

Maximum number of answers:"; - html = html + "

Required:"; - html = html+ this.makeRadio('required',[{text:'Yes',value:1},{text:'No',value:0}],params.required); - html = html + "\ -

\ -
\ -
\ - "; - - document.getElementById('edit').innerHTML = html; - - - var butts = [ { text:"Submit", handler:function(){this.submit();}, isDefault:true }, { text:"Cancel", handler:function(){this.cancel();}} ]; - if(params.Survey_questionId != ''){ - butts[2] = {text:"Delete", handler:function(){Survey.Comm.deleteQuestion(params.Survey_questionId);}}; - } - - var form = new YAHOO.widget.Dialog("question", - { width : "500px", - fixedcenter : true, - visible : false, - constraintoviewport : true, - buttons : butts - } ); - - form.callback = Survey.Comm.callback; - form.render(); - form.show(); - - } - this.makeMenu = function(name,values,text,selected){ - var html = "\n"; - return html; - } - this.makeRadio = function(name,values,checked){ - var html = ''; - for(var i in values){ - if(checked == values[i]['value']){ - html = html+ "" + values[i]['text']; - }else{ - html = html+ "" + values[i]['text']; - } - } - html = html + "\n"; - return html; - } - -}(); diff --git a/www/extras/wobject/Survey/editsurvey/section.js b/www/extras/wobject/Survey/editsurvey/section.js deleted file mode 100644 index 91d21a00a..000000000 --- a/www/extras/wobject/Survey/editsurvey/section.js +++ /dev/null @@ -1,28 +0,0 @@ -if (typeof Survey == "undefined") { - var Survey = {}; -} - -Survey.SectionTemplate = new function(){ - - this.loadSection = function(html){ - - document.getElementById('edit').innerHTML = html; - - var butts = [ { text:"Submit", handler:function(){this.submit();}, isDefault:true }, { text:"Cancel", handler:function(){this.cancel();}}, - {text:"Delete", handler:function(){document.getElementById('delete').setValue(1); this.submit();}} - ]; - - var form = new YAHOO.widget.Dialog("section", - { width : "500px", - fixedcenter : true, - visible : false, - constraintoviewport : true, - buttons : butts - } ); - - form.callback = Survey.Comm.callback; - form.render(); - form.show(); - } -}(); - From c402227d1672e8515a86d07a0a4a707f8d86805c Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 8 Jan 2009 03:15:05 +0000 Subject: [PATCH 10/90] Removed SimpleEditor title, improved handling of editor window when it is bigger than viewable screen --- www/extras/wobject/Survey/editsurvey/object.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 7d9b9d583..02c05bf12 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -25,7 +25,7 @@ Survey.ObjectTemplate = new function(){ var form = new YAHOO.widget.Dialog(type, { width : "500px", - fixedcenter : true, + context: [document.body, 'tr', 'tr'], visible : false, constraintoviewport : true, buttons : butts @@ -49,7 +49,9 @@ Survey.ObjectTemplate = new function(){ width: width, dompath: false //Turns on the bar at the bottom }); + myTextarea.get('toolbar').titlebar = false; myTextarea.render(); + form.show(); } From 47d16d3baab1b9b5d9fdf195a4eb1eff3415b98c Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 8 Jan 2009 06:22:37 +0000 Subject: [PATCH 11/90] Combined the folllowing RFE packages into a single Survey folder package: rfe-9202 * Default Answer Edit * Default Survey Edit * Default Answer Edit rfe-9199 * Default Questions rfe-9198 * Default Answer Edit * Default Question Edit * Default Section Edit Also cleaned up invalid html mark-up in Section/Question/Answer edit templates --- ...oot_import_survey_default-answer-edit.wgpkg | Bin 1401 -> 0 bytes ...t_import_survey_default-question-edit.wgpkg | Bin 1598 -> 0 bytes ...ot_import_survey_default-section-edit.wgpkg | Bin 1614 -> 0 bytes rfe9199-survey-default-questions.wgpkg | Bin 2549 -> 0 bytes rfe9202-survey.wgpkg | Bin 8874 -> 0 bytes root_import_survey.wgpkg | Bin 0 -> 9123 bytes www/extras/wobject/Survey/editsurvey/object.js | 17 ++++++++--------- 7 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 rfe-9198-root_import_survey_default-answer-edit.wgpkg delete mode 100644 rfe-9198-root_import_survey_default-question-edit.wgpkg delete mode 100644 rfe-9198-root_import_survey_default-section-edit.wgpkg delete mode 100644 rfe9199-survey-default-questions.wgpkg delete mode 100644 rfe9202-survey.wgpkg create mode 100644 root_import_survey.wgpkg diff --git a/rfe-9198-root_import_survey_default-answer-edit.wgpkg b/rfe-9198-root_import_survey_default-answer-edit.wgpkg deleted file mode 100644 index 5e32dd3a32ca1405c2a7afa0b51955a758ea84b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401 zcmV-<1%~<`iwFP!000001MOE$bK5o$)j7X{qYK-aBqH@0DUv#_n>3y#vL|+uPU|)U zkt-242`~UCS?T1zcL9(hE!s}vvD=GzuqlAM`}XYuczD{l|Ht#ZL9f?nc%DD#`He|4ucn#n8J6^xn>-YN&$M^ic*Kp9}{+~=LA`sdd=F5!UJ{~5Ck51D5zrCmb)Y72! z%fFqWe)qknXOgo!LV^za-R`yZA9z^v%k}ScyX}U9Y~KHKU-f@_|IqIu!at925jLgb zf-IrwjGRCJdTfSL!BY^5g4CntGyP>CfG7xm-ssV0r)fUc6ETtM#YM^m&h8l-mc{XQ z8dsSz{+nqwkEx6xENvOAKTsC(wQ=f|6LQ0NDrwyvUo#NzB#47>7YJM^L0$m~Psp-v zNTdXn8||G%@$9g({$n-kop)*X6XhR*Wf?sav2iXqS1p?8+*&ddE7-I`Sdc7MPY9E1 z5Kkbas6%9jR#@Mfp9|^9w+N73=N}@}_v-BW*q5WuL{dQ@lAW7qby>Uoa4h<)+ zjqFZO#VIp`0aidx+$eOXe;+Uff)PbIBk>Pz8UvBwfF5$zJq^}r<5Q75O6rgyw&b%!%H)o`3+w;_3}&}e-CrJDgIfOq>kcO#I00PCsME75|`98udaS~B>fDNZW&FcgqezI z`y(PqA`x#|f$SR3mPL13X)ZV5vHo}$gbC5EW6^&0(WJn${ay7b_!@ocHraXfy3#}6 z749v1tA;bbopB5EYw65O9_m%6(ovE50}j?7E2pk)fCAhuKv$5E^6l!9`_@mf$oxEW zx^QSQHw_{P&LPCs)T@{-rnU9I0a@>bTSD-12pNk(%4=cR={gj4MOPL`uGqN}%Ie5# z6|L<+XjW7aUu_S!Z>n8FE^ZZvTx{czi*JY{6{NQbr0t4G`u!V1tw7B2KuYT23chb{ zzO8+}Fe|-ROw}4KZ^e4Z`)SSsN3lG1db61RZkTt}y!o+fy7LN3>^6v<7Xw?WXySXe zs(pLXnq9Qk*=C^V1p7npPP(MymX;3r>sE0X?NQ1f$|4X{y%X_E!4urSQN|dJRFO|H zWsvLFW_g#vYYaZcji&Tyl+p~Pi*fS-=D)r>7>$es8jVg0wRKs#?ni_zuhYG6ct{r% z6|I$S9lzZj;0?BnsHloW1?Pg@|5_v()+;BJ;4ts-kg>eBZDp7&{QV3_iT`vojM#McMxB{rvJqcWu>FP^J`rNJuZ*5xt+3&bWxsbOza%?d0OH-mq&fs z-Cy?(pF9f|arAm!QcC6V2KUWmH+hp-!B_PHh8<$AUsv^rxwTzpquuwsVZZmRjEF_V zrIo|$&Drd;g4tl*nX%u7$;@|H;6=kGOqB H02lxO<`}{+ diff --git a/rfe-9198-root_import_survey_default-question-edit.wgpkg b/rfe-9198-root_import_survey_default-question-edit.wgpkg deleted file mode 100644 index 19086d8a6782934ee20061859f5daa03b7fa61ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1598 zcmV-E2Eq9siwFP!000001MOH*Z`(Ey_H%y);fHAh+D3})7Jmz9S`CidnmDVDDjw9uk@4-F^J-NZsAj+V&sc_uH*jt>*hdyA{-G zjduQ^#pBn4AZYk|tybIjYbfx8W~+ukWcyDh6&3((Mn>a|-rpW<5+1$D{Qvzm{il)! zZBYJgbj*9_>6PScLr6g5UbDH%|KRx6-je?t%~rF9fX(ec+w%X@?L*&$g?}EwA*@Tq z1s;<+>Y_jYdTfSL!BZj>B~p)?WqM^O2v#I|KG&o5M%^sd6ET+R3%G80oW--*a2&SLfC zQ%0nsoIN2CRiz}AuW1w!X2gT)__5%Mt9hDpQQ7KP@GL!yUTQs>+F-X7QzS`a%U>^| zQ$(Y_XKj0fb8*hha0p(IeQy$ZgTD`%B7$L!4#z)wX-u#ri0DydMG<9}TqI_Z_t@Ll z+OIkNBIbpYlHgo?rCYna6 zY?M&-eso1brT1CUlR9`85LuRth;Y%No&r`brdXheO5kTsq6-?8*eFXgg*4Z|h;fHk zK&6p~rZ~=EqErH+6+o+nBN7uuJmUiUo-6ae#dgTkc`ls4IihQ*P!QWL5esMNX$d3{h4qxB4WJ63zS z$u&s9D-@4nVxYK?dpW%czKIDNt4Y7nLXzH+e&FX0)5csES*?S@k@j~ky9*%Gb?5|! zJ0<}$2s1e&Lb|{#mnOKYF(7b6xs9KNqWwZo0W~OMl3t?aB?A;rNO(;m1Wf9utC&vm$WOm%wOL|1KD$1ky5UE;vm zAi+3}f#HxR2_!T3rPe4pD0H~gR;Qi4Z20P~+FC(vb%_J#d#N>tT&9?r6sF+u7z><8 zZvX-P0~^WoS^gIt@K|<>&$UUVw3mzTt$W=T%CwOEMpuW>?|de^_UvdJ`uSe%TFH`j zGX!7O6jA`avvs#2Bpo!#AZs;wCE}N>vjGR0lQe>|A_uUD$`PBD*!_Ott`Ou?Mg@uP z8^sHGH-hby6D}K$UTbGX>^X7E^HN*>nSC9{a(>QrCzEFf!s`~U0&1lh%0~)n(A|z9k|=G;kP2t z&4tX=`gNQ_<6>HEE|`k{1hRf(qR|d2_);vOhhQdFP}=Rj9=?0)5=`Y=uG9Iv5pt*1 zL1jVM@}5i&q;QIv>vYTmr_x{0xyK=U z1p%6~QJK<3I$VumPD5Vf@7#4HVWF6(>ewo-z*2ZzG)*o#=_$E|)=9wLT+gmPzH6La zzpXS)s63fVXjm+7-fYbBRc#zWjF`63m!iaUo9?o)yXX6zy;cPqI1!Fl0k1V;N4JU` wwP%eZ_NJ4(YJQ{#@|AT>l0n_*JfB+l-02mD@{{R30 diff --git a/rfe-9198-root_import_survey_default-section-edit.wgpkg b/rfe-9198-root_import_survey_default-section-edit.wgpkg deleted file mode 100644 index 09c5c616b8afca71bd07244d7b62d84dee063781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1614 zcmV-U2C?}ciwFP!000001MOH_Z`(E)^>cp(;TLT{Q%k$&+JiMJ}wZlKQZTC8zTFthdUdO4`TD|l^ z`D51{$7$K!PN!%0YRIvjPP>M{g)HPo_!J#+fydsvDt~uOcKtpN)87SAT}M3BeXOU}u|nqR zT-Rs)_P7GrE<3!tAX%HKqN7a%&*j$U6ga1xf{hRW1PNMOm`pw z=x@Uz4VI3Lq%wf?D%I)lV=IM2!n7%J^?C7L4bCyl)sTK6=--40MKMv$L9xUzMYE+` zKgS-8OwqZ!-*RtRvQ9Rj^cqz=jgjjU_cQU(u%U;YUXLq`S&>^(Qz@xu_zh{S+qQ`_cv*ob!##N;@GQFDDXhc)j&i7MA7NgYNPcV~h*ZxQTqD2A=C3Y{VE zP{90lWwuV_mS=>YDZ3m8%_dF6Ad2E*YGfVP`suz=tfo#CeAo%e4HT8GL%{ts0pg9# z%g8BFzO-Cv!(k(JycRI)uQJQO4;!gJ{Q)|7cP+np?FiB~W4s+aAa0IdHaQDKPT6_7 zZ7Klse!FL59(&E$tZEL*7lVB+0lS0Rl~A`$=Ykp}5PPb)68t-sx;yIJ0q)L<^JO`w zLbk8uzpKle-jo!-$b#_IT~pIgOLtamx=?%FJt?(rbYLoN4|ZS*Z4c_~lRDc&*!!c* z7K*@8{ z8>ah#39%XGz+Bi2w^tf$I)6|GAh^a?S+I8SSC^mkd}lI!&x7j0tkjs@3)53mqjZU_ zE?4HDq=HoQP$y}m_Cxh=sQO?~ShB;WHbFwJ0y4U+FR3T}LAzteH_v@S=e`_(a0fpw z>qsdAm-Q^v1$eov!)eZ3hLs;Uvm5W4=%xF8e$!Vu6tV+lKH*dX6em0i&mb?BY7-^d zBA|@GwyPzJLPWH+plGDT!lM=_rf_A|{XdY|FYhOVffgPHgA1c_tZPOueaz;!2~O`K zk6u%dl2XZ}ZR zvT~Z)z;p`is5ry!XG5KmMkf4dIbkQDC#7hVn$%TP#Mn(?NR_I_`ZyBi@>ML=QE)SL ziRYQ_H|MM4kC^@WE+AJjo=ra-`2jEKepH;V1c+{%9&}*_ud?;1PjG1OS2m M0n=}?CjcG*0LHf?W&i*H diff --git a/rfe9199-survey-default-questions.wgpkg b/rfe9199-survey-default-questions.wgpkg deleted file mode 100644 index 22e638b7c5955987ad4abfb5650c65b0910207b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2549 zcmVExUL9@3*@E2=FPzv2$}X;fyU3kA3fA0qnEAr+@nW{%APd+w1oSqv2q0 z?_eY!Cx7~TgTY{b|L|}y8XfI9gZ^N6xaYu>r++e$dI^xWVY6k%AC3>0ga?_FzYmA> zRc8{l1M~mjnBGUvK8h%OBuW6{(cxi}{`U_?N5hi-4-QBDJqM)u^v_f2|Jl=%{umhk z{T(=AN2F2WEm_AoasK(Q@2M$HqL?KqX98Pk7>=AL%uAX7;vQQ&2OS!W9f2p(S9fug zK<^pN39}%`yTQv+g5Qa=S-{1L`DSj&HXnHCM;ij#Hyz?WjAFs7;G#{)lJ|lo7k-%# zc9$mJ8|L{p-m*H;6N05`()r!n_|>oPuf6OaX9w}~kACv=*^7p0M|8~gm$;r{8gBkAa_fKQf8JCkWL4Rtuxqhx2w(|}FJDS-L))=QkwTeDM_ zTHVRG2N2sPgEXsiq0yddfJiKSA&~%;t7rpUFB2w&B66B)@}?cIv0@i=mgvj{d=`og zOPKFYoG443XyKHHf^5CuFIpikaRmT#4#z!$%@@>DN9qk4RvUcGf)1QYJ3h z0wFl+(+Th!^wUC&(yN=ZtH_60<3*ILmDZ%zUqgfIfYD_$pcRZEVQH3x&cX`>`$5x= zQ;Fdqiefdt1Rf2Bv@r+o6{k6NiF_PS#v=AYG&r{`Yz~S@*l!spm5h5hu+dNeP_6}S zsQ6pdfx8Be;&H&vR}r5xNknebN_UEpwRiV6O4!e#8aDn@x2uP0t|o#7Y);-uDmZc4 z*#Vef)g}9bMrnCMTYR@$^p}EF5$-|@DIR#m9?g4+?*$LoWrU7g$FrqAk9+_q(SQ&r zK4q$JA#&FZBXvq;OIQ_11dWF=xEMyNQx}PImDEdUrXB8swYhZ$!hL8L{C9vb?7L2{ zf=HQ6JtsPXxWLHFOZmFWG7(Bv=Zr0SQ;1Cx@31eac4}=m2kd}t5||1lxf+e)5Gg(- zK_HoF*6XC2LsG>R}j`-8)%fq!y0p_Z=_b z%Mc7|&cYNkKgZc{KV6-UhVfnNWJ8c`?OfMhz+RoTUi*V#>^;pEp9Jt`t^G$ypd(KThtCz;EpA?RTEvbhilyBUm(Ag zJw4u*J$gc(HZ}@PRI~lqc)wPpMf@dY?j9d#vfNf%i`Ekas)qfKEj*%Shj66EHOJovIcB|Lp%v4% z+!TtV@b72dVY|fL23S9wH#oKq;AVuCD?LI+$o$cU5B9 zxUHbIW5+~+j({eLB|4vga{@Z)AkS)M(l~B|zZm=b;`0I&jn;+%i!FI;k!KO`hSQ^5 zEPmz%@2p`Ol2(IMtl6u$-k^Q~t_h?~ifcGGQl=r()qcew6vr*8i%sSJSD~`HSE19w znoPS)eMQb>>aAyRc<@>X1yvD|`fV{ZDXZ17@Oa*x^xF-(UJoh&Vf>*;v;&Dvn%aTH za@3JWwXNrNEo9e)Ocit|{Xd5@5&t0nH#653Sfw*|XG@5F-VE-w*xjky9l#gf#GJay z3)RLsAH9PFo0Pl*HELY?SX}jeg^kUY5y)10xIOWUapT~iykXc<>{*z1kvU^TCd^Q+hlxtFd>a6svj8;(5Wz0%~EX;KW!f&Z_$}H7q<_v4p4Zli+m{t*=2bp)7Q;EZUwPrOsdVXWOleqyEA3{pSWFC2!RGQr7-Bd8vsRqzZZd%9$)>8ksIGu)GHyP@*tB;uDXrq7=~2 z;i@~5g+rEPz~r+VOdYpj#~Q1QN}f7i47D}4c8wJkKD~nrd4lr%Xmn&Kh`j_#si>nt zlqV-|Zr)y21q*f;%|B_O7An5DP=X@37C%%9rj$W_$H$}N&l? zO1QZ8=AX3DSfy6Duek`ZOh!t>>cR~^tgLe>29>Sec%PV&1`FrOeN4_&CO!3*s&=e< z`{Vj}nH}DIIQuC+T>SmT%`aj9%mCry-MxU?tuh2+Ov}rzp9A}#>bW`#p`yEzG@zpP zXn0UmBDj?zZ#WCDeoxueXmfBC{&c*4efT$izU*LS-yh%o@fd}J-(Y-`z&8o}-%H@X L|Aase05AXmFstjm diff --git a/rfe9202-survey.wgpkg b/rfe9202-survey.wgpkg deleted file mode 100644 index 739bb1699779d4330a31cdb35061337b4e2b30e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8874 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 diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..a2cb1e8245c78bb9b7ec833a334d2270b918dfdc GIT binary patch literal 9123 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 literal 0 HcmV?d00001 diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 02c05bf12..43fc3f4b7 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -24,7 +24,7 @@ Survey.ObjectTemplate = new function(){ var form = new YAHOO.widget.Dialog(type, { - width : "500px", + width : "600px", context: [document.body, 'tr', 'tr'], visible : false, constraintoviewport : true, @@ -36,22 +36,21 @@ Survey.ObjectTemplate = new function(){ var textareaId = type+'Text'; var textarea = YAHOO.util.Dom.get(textareaId); + var height = YAHOO.util.Dom.getStyle(textarea,'height'); if (height == ''){ - height = '300px'; - } - var width = YAHOO.util.Dom.getStyle(textarea,'width'); - if (width == ''){ - width = '500px'; + height = '300px'; } myTextarea = new YAHOO.widget.SimpleEditor(textareaId, { height: height, - width: width, + width: '100%', dompath: false //Turns on the bar at the bottom }); - myTextarea.get('toolbar').titlebar = false; - myTextarea.render(); + if (myTextarea.get('toolbar')) { + myTextarea.get('toolbar').titlebar = false; + } + myTextarea.render(); form.show(); } From 11ff75f778f787f133411ca50d26456a78ab870b Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 9 Jan 2009 03:27:17 +0000 Subject: [PATCH 12/90] Gracefully handle Survey question with no answers --- www/extras/wobject/Survey/administersurvey.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index 1d4bf27e2..f45fcf544 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -389,6 +389,10 @@ if (typeof Survey === "undefined") { addWidgets: function(qs){ hasFile = false; for (var i = 0; i < qs.length; i++) { + if (!q || !q.answers) { + // gracefully handle q with no answers + continue; + } var q = qs[i]; var verts = ''; for (var x in q.answers) { From 8a4f26215a7921adb44c323abcc82274643aff4f Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 9 Jan 2009 03:37:32 +0000 Subject: [PATCH 13/90] Handle Survey qs without answers even more gracefully (e.g. by not breaking) --- www/extras/wobject/Survey/administersurvey.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index f45fcf544..b48d0fb26 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -389,11 +389,12 @@ if (typeof Survey === "undefined") { addWidgets: function(qs){ hasFile = false; for (var i = 0; i < qs.length; i++) { + var q = qs[i]; if (!q || !q.answers) { // gracefully handle q with no answers continue; } - var q = qs[i]; + var verts = ''; for (var x in q.answers) { if (YAHOO.lang.hasOwnProperty(q.answers, x)) { From 63e74f14f4d49e9a340ebfbdb68ae998f55c4a3b Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 9 Jan 2009 07:13:15 +0000 Subject: [PATCH 14/90] Added "Preview" button for survey builders --- lib/WebGUI/Asset/Wobject/Survey.pm | 48 +++++++++++++++++++ www/extras/wobject/Survey/editsurvey.js | 2 +- .../wobject/Survey/editsurvey/object.js | 17 +++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 7be6c74f3..f123820cf 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -298,6 +298,54 @@ sub www_submitObjectEdit { return $self->www_loadSurvey( { address => \@address } ); } ## end sub www_submitObjectEdit +#------------------------------------------------------------------- +=head2 Allow survey editors to "jump to" a particular section of question in a +Survey by tricking Survey into thinking they've completed the survey up to that +point. Useful for survey builders. +Note that calling this method will delete any existing survey responses for the +current user (although only survey builders can call this method so that shouldn't be +a problem +=cut + +sub www_jumpTo { + my $self = shift; + + return $self->session->privilege->insufficient() + unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + + my $data = $self->session->form->paramsHashRef(); + + $self->session->log->debug("jumpTo to $data->{id}"); + + # Remove existing responses for current user + $self->session->db->write( 'delete from Survey_response where assetId = ? and userId = ?', + [ $self->getId, $self->session->user->userId() ] ); + my $responseId = $self->getResponseId(); + + $self->loadBothJSON(); + + # iterate over surveyOrder looking for the jumpTo target + for my $i ( 0 .. $#{ $self->response->surveyOrder() } ) { + my $address = $self->response->surveyOrder()->[$i]; + + my @possibilities = ( + $self->survey->section($address), + $self->survey->question($address), + ); + foreach my $possibilty (@possibilities) { + if ( ref $possibilty eq 'HASH' && $possibilty->{id} eq $data->{id} ) { + $self->session->log->debug("Found jumpTo target"); + $self->response->lastResponse( $i - 1 ); + $self->saveResponseJSON(); + last; + } + } + } + $self->session->log->debug("Unable to find jumpTo target"); + + return $self->www_takeSurvey; +} + #------------------------------------------------------------------- sub copyObject { my ( $self, $address ) = @_; diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index a1fbcbebf..f2cac509e 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -133,7 +133,7 @@ Survey.OnLoad = function() { initHandler: function(){ new YAHOO.util.DDTarget("sections","sections"); Survey.Comm.loadSurvey(); - }, + } } }(); diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 43fc3f4b7..e356c2326 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -21,8 +21,14 @@ Survey.ObjectTemplate = new function(){ { text:"Cancel", handler:function(){this.cancel();}}, { text:"Delete", handler:function(){document.getElementById('delete').value = 1; this.submit();}} ]; + if (type !== 'answer') { + butts.push({ + text: "Preview", + handler: jumpTo + }); + } - var form = new YAHOO.widget.Dialog(type, + var dialog = new YAHOO.widget.Dialog(type, { width : "600px", context: [document.body, 'tr', 'tr'], @@ -31,8 +37,11 @@ Survey.ObjectTemplate = new function(){ buttons : butts } ); - form.callback = Survey.Comm.callback; - form.render(); + dialog.callback = Survey.Comm.callback; + dialog.render(); + function jumpTo() { + window.location.search = 'func=jumpTo;id=' + dialog.form.id.value; + } var textareaId = type+'Text'; var textarea = YAHOO.util.Dom.get(textareaId); @@ -52,7 +61,7 @@ Survey.ObjectTemplate = new function(){ } myTextarea.render(); - form.show(); + dialog.show(); } }(); From 021bac39eca7eae749e286f8f9963211d539abc0 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 9 Jan 2009 07:13:35 +0000 Subject: [PATCH 15/90] Refactored editsurvey js --- www/extras/wobject/Survey/editsurvey.js | 272 ++++++++++-------- .../wobject/Survey/editsurvey/object.js | 143 +++++---- 2 files changed, 228 insertions(+), 187 deletions(-) diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index f2cac509e..cb2f6d05a 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -1,140 +1,162 @@ -if (typeof Survey == "undefined") { +/*global Survey, YAHOO */ +if (typeof Survey === "undefined") { var Survey = {}; } -Survey.Data = new function(){ +Survey.Data = (function(){ + var lastDataSet = {}; var focus; var lastId = -1; - - this.dragDrop = function(did){ - var type; -YAHOO.log('In drag drop'); - if(did.className.match("section")){type = 'section';} - else if(did.className.match("question")){type = 'question';} - else{ type = 'answer';} - - var first = {id:did.id,type:type}; - var before = document.getElementById(did.id).previousSibling; - - while(1){ - if( before == undefined || (before.id != undefined && before.id != '') ){ - break; + + return { + dragDrop: function(did){ + var type; + YAHOO.log('In drag drop'); + if (did.className.match("section")) { + type = 'section'; } - var before = before.previousSibling; - } - - var data = {id:'',type:''}; - - if(before != undefined && before.id != undefined && before.id != ''){ - if(before.className.match("section")){type = 'section';} - else if(before.className.match("question")){type = 'question';} - else{ type = 'answer';} - data = {id:before.id,type:type}; - } -YAHOO.log(first.id+' '+data.id); - Survey.Comm.dragDrop(first,data); - } - - - - this.clicked = function(){ - Survey.Comm.loadSurvey(this.id); - } - - - - this.loadData = function(d){ - focus = d.address;//What is the current highlighted item. - var showEdit = 1; - if(lastId.toString() == d.address.toString()){ - showEdit = 0; - lastId = -1; - }else{ - lastId = d.address; - } - document.getElementById('sections').innerHTML=d.ddhtml; + else + if (did.className.match("question")) { + type = 'question'; + } + else { + type = 'answer'; + } + + var first = { + id: did.id, + type: type + }; + var before = document.getElementById(did.id).previousSibling; + + while (1) { + if (before === undefined || (before.id !== undefined && before.id !== '')) { + break; + } + before = before.previousSibling; + } + + var data = { + id: '', + type: '' + }; + + if (before && before.id !== '') { + if (before.className.match("section")) { + type = 'section'; + } + else + if (before.className.match("question")) { + type = 'question'; + } + else { + type = 'answer'; + } + data = { + id: before.id, + type: type + }; + } + YAHOO.log(first.id + ' ' + data.id); + Survey.Comm.dragDrop(first, data); + }, - //add event handlers for if a tag is clicked - for(var x in d.ids){ -YAHOO.log('adding handler for '+ d.ids[x]); - YAHOO.util.Event.addListener(d.ids[x], "click", this.clicked); - new Survey.DDList(d.ids[x],"sections"); - } + clicked: function(){ + Survey.Comm.loadSurvey(this.id); + }, - //add the add object buttons -// if(d.buttons['section']){ + loadData: function(d){ + focus = d.address;//What is the current highlighted item. + var showEdit = 1; + if (lastId.toString() === d.address.toString()) { + showEdit = 0; + lastId = -1; + } + else { + lastId = d.address; + } + document.getElementById('sections').innerHTML = d.ddhtml; + + //add event handlers for if a tag is clicked + for (var x in d.ids) { + if (YAHOO.lang.hasOwnProperty(d.ids, x)) { + YAHOO.log('adding handler for ' + d.ids[x]); + YAHOO.util.Event.addListener(d.ids[x], "click", this.clicked); + var _s = new Survey.DDList(d.ids[x], "sections"); + } + } + + //add the add object buttons + // if(d.buttons['section']){ document.getElementById('addSection').innerHTML = ''; document.getElementById('addQuestion').innerHTML = ''; document.getElementById('addAnswer').innerHTML = ''; - var button = new YAHOO.widget.Button({ label:"Add Section", id:"addsection", container:"addSection" }); - button.on("click", this.addSection); -// } -// if(d.buttons['question']){ - var button = new YAHOO.widget.Button({ label:"Add Question", id:"addquestion", container:"addQuestion" }); - button.on("click", this.addQuestion,d.buttons['question']); -// } - if(d.buttons['answer']){ - var button = new YAHOO.widget.Button({ label:"Add Answer", id:"addanswer", container:"addAnswer" }); - button.on("click", this.addAnswer,d.buttons['answer']); - } - - if(showEdit == 1){ - this.loadObjectEdit(d.edithtml,d.type); - - // build the goto auto-complete widget - if (d.gotoTargets && document.getElementById('goto')) { - var ds = new YAHOO.util.LocalDataSource(d.gotoTargets); - var ac = new YAHOO.widget.AutoComplete('goto', 'goto-yui-ac-container', ds); - } - }else{ - document.getElementById('edit').innerHTML = ""; - } - lastDataSet = d; - } - - this.addSection = function(){ - Survey.Comm.newSection(); - } - - - this.addQuestion = function(e,id){ - Survey.Comm.newQuestion(id); - } - - this.addAnswer = function(e,id){ - Survey.Comm.newAnswer(id); - } - - this.loadObjectEdit = function(edit,type){ - if(edit){ - Survey.ObjectTemplate.loadObject(edit,type); - } - } - - - this.loadLast = function(){ - this.loadData(lastDataSet); - } -}(); - - -//---------------------------------------------------------------- -// -// Initialize survey -// -//---------------------------------------------------------------- -Survey.OnLoad = function() { - var e = YAHOO.util.Event; - return { - init: function() { - e.onDOMReady(this.initHandler); + var sButton = new YAHOO.widget.Button({ + label: "Add Section", + id: "addsection", + container: "addSection" + }); + sButton.on("click", this.addSection); + // } + // if(d.buttons['question']){ + var qButton = new YAHOO.widget.Button({ + label: "Add Question", + id: "addquestion", + container: "addQuestion" + }); + qButton.on("click", this.addQuestion, d.buttons.question); + // } + if (d.buttons.answer) { + var aButton = new YAHOO.widget.Button({ + label: "Add Answer", + id: "addanswer", + container: "addAnswer" + }); + aButton.on("click", this.addAnswer, d.buttons.answer); + } + + if (showEdit == 1) { + this.loadObjectEdit(d.edithtml, d.type); + + // build the goto auto-complete widget + if (d.gotoTargets && document.getElementById('goto')) { + var ds = new YAHOO.util.LocalDataSource(d.gotoTargets); + var ac = new YAHOO.widget.AutoComplete('goto', 'goto-yui-ac-container', ds); + } + } + else { + document.getElementById('edit').innerHTML = ""; + } + lastDataSet = d; }, - initHandler: function(){ - new YAHOO.util.DDTarget("sections","sections"); - Survey.Comm.loadSurvey(); + + addSection: function(){ + Survey.Comm.newSection(); + }, + + addQuestion: function(e, id){ + Survey.Comm.newQuestion(id); + }, + + addAnswer: function(e, id){ + Survey.Comm.newAnswer(id); + }, + + loadObjectEdit: function(edit, type){ + if (edit) { + Survey.ObjectTemplate.loadObject(edit, type); + } + }, + + loadLast: function(){ + this.loadData(lastDataSet); } - } -}(); + }; +})(); -Survey.OnLoad.init(); +// Initialize survey +YAHOO.util.Event.onDOMReady(function(){ + var ddTarget = new YAHOO.util.DDTarget("sections", "sections"); + Survey.Comm.loadSurvey(); +}); diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index e356c2326..cae2d2f8b 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -1,67 +1,86 @@ -if (typeof Survey == "undefined") { + +/*global Survey, YAHOO */ +if (typeof Survey === "undefined") { var Survey = {}; } -Survey.ObjectTemplate = new function(){ +Survey.ObjectTemplate = (function(){ - this.loadObject = function(html,type){ - - document.getElementById('edit').innerHTML = html; - - var myTextarea; - - var handleSubmit = function(){ - myTextarea.saveHTML(); - this.submit(); - } - - var butts = [ - { text:"Submit", handler:handleSubmit, isDefault:true }, - { text:"Copy", handler:function(){document.getElementById('copy').value = 1; this.submit();}}, - { text:"Cancel", handler:function(){this.cancel();}}, - { text:"Delete", handler:function(){document.getElementById('delete').value = 1; this.submit();}} - ]; - if (type !== 'answer') { - butts.push({ - text: "Preview", - handler: jumpTo - }); - } - - var dialog = new YAHOO.widget.Dialog(type, - { - width : "600px", - context: [document.body, 'tr', 'tr'], - visible : false, - constraintoviewport : true, - buttons : butts - } ); - - dialog.callback = Survey.Comm.callback; - dialog.render(); - function jumpTo() { - window.location.search = 'func=jumpTo;id=' + dialog.form.id.value; - } - - var textareaId = type+'Text'; - var textarea = YAHOO.util.Dom.get(textareaId); - - var height = YAHOO.util.Dom.getStyle(textarea,'height'); - if (height == ''){ - height = '300px'; - } - myTextarea = new YAHOO.widget.SimpleEditor(textareaId, { - height: height, - width: '100%', - dompath: false //Turns on the bar at the bottom - }); - - if (myTextarea.get('toolbar')) { - myTextarea.get('toolbar').titlebar = false; - } - myTextarea.render(); - - dialog.show(); - } -}(); + var editor; + var dialog; + + return { + + loadObject: function(html, type){ + + document.getElementById('edit').innerHTML = html; + + var btns = [{ + text: "Submit", + handler: function(){ + editor.saveHTML(); + this.submit(); + }, + isDefault: true + }, { + text: "Copy", + handler: function(){ + document.getElementById('copy').value = 1; + this.submit(); + } + }, { + text: "Cancel", + handler: function(){ + this.cancel(); + } + }, { + text: "Delete", + handler: function(){ + document.getElementById('delete').value = 1; + this.submit(); + } + }]; + + dialog = new YAHOO.widget.Dialog(type, { + width: "600px", + context: [document.body, 'tr', 'tr'], + visible: false, + constraintoviewport: true, + buttons: btns + }); + + if (type !== 'answer') { + btns.push({ + text: "Preview", + handler: function(){ + window.location.search = 'func=jumpTo;id=' + dialog.getData().id; + } + }); + } + + dialog.callback = Survey.Comm.callback; + dialog.render(); + + var textareaId = type + 'Text'; + var textarea = YAHOO.util.Dom.get(textareaId); + + var height = YAHOO.util.Dom.getStyle(textarea, 'height'); + if (!height) { + height = '300px'; + } + editor = new YAHOO.widget.SimpleEditor(textareaId, { + height: height, + width: '100%', + dompath: false //Turns on the bar at the bottom + }); + + if (editor.get('toolbar')) { + editor.get('toolbar').titlebar = false; + } + editor.render(); + + dialog.show(); + } + }; +})(); From 34b44def2fa3fdfd3287db00d53d07d943a686a1 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 14 Jan 2009 06:14:37 +0000 Subject: [PATCH 16/90] Added gotoExpression tests (from flux branch) --- t/Asset/Wobject/Survey/ResponseJSON.t | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index a75f5b47c..803cac720 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -324,26 +324,26 @@ is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); # processGotoExpression # #################################################### -is($rJSON->processGotoExpression(), +is($rJSON->processGotoExpression(), undef, 'processGotoExpression undef with empty arguments'); -is($rJSON->processGotoExpression('blah-dee-blah-blah'), +is($rJSON->processGotoExpression('blah-dee-blah-blah'), undef, '.. and undef with duff expression'); -is($rJSON->processGotoExpression(':'), +is($rJSON->processGotoExpression(':'), undef, '.. and undef with missing target'); -is($rJSON->processGotoExpression('t1:'), +is($rJSON->processGotoExpression('t1:'), undef, '.. and undef with missing expression'); -cmp_deeply($rJSON->processGotoExpression('t1: 1'), +cmp_deeply($rJSON->processGotoExpression('t1: 1'), { target => 't1', expression => '1'}, 'works for simple numeric expression'); -cmp_deeply($rJSON->processGotoExpression('t1: 1 - 23 + 456 * (78 / 9.0)'), +cmp_deeply($rJSON->processGotoExpression('t1: 1 - 23 + 456 * (78 / 9.0)'), { target => 't1', expression => '1 - 23 + 456 * (78 / 9.0)'}, 'works for expression using all algebraic tokens'); is($rJSON->processGotoExpression('t1: 1 + &'), undef, '.. but disallows expression containing non-whitelisted token'); -cmp_deeply($rJSON->processGotoExpression('t1: 1 = 3'), +cmp_deeply($rJSON->processGotoExpression('t1: 1 = 3'), { target => 't1', expression => '1 == 3'}, 'converts single = to =='); -cmp_deeply($rJSON->processGotoExpression('t1: 1 != 3 <= 4 >= 5'), +cmp_deeply($rJSON->processGotoExpression('t1: 1 != 3 <= 4 >= 5'), { target => 't1', expression => '1 != 3 <= 4 >= 5'}, q{..but doesn't mess with other ops containing =}); -cmp_deeply($rJSON->processGotoExpression('t1: q1 + q2 * q3 - 4', { q1 => 11, q2 => 22, q3 => 33}), +cmp_deeply($rJSON->processGotoExpression('t1: q1 + q2 * q3 - 4', { q1 => 11, q2 => 22, q3 => 33}), { target => 't1', expression => '11 + 22 * 33 - 4'}, 'substitues q for value'); -cmp_deeply($rJSON->processGotoExpression('t1: a silly var name * 10 + another var name', { 'a silly var name' => 345, 'another var name' => 456}), +cmp_deeply($rJSON->processGotoExpression('t1: a silly var name * 10 + another var name', { 'a silly var name' => 345, 'another var name' => 456}), { target => 't1', expression => '345 * 10 + 456'}, '..it even works for vars with spaces in their names'); is($rJSON->processGotoExpression('t1: qX + 3', { q1 => '7'}), undef, q{..but doesn't like invalid var names}); From 42ce45f82531218780d1637cc5dc712eba7865a1 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 14 Jan 2009 06:14:52 +0000 Subject: [PATCH 17/90] More code from flux branch --- www/extras/wobject/Survey/administersurvey.js | 4 ++-- www/extras/wobject/Survey/surveyedit.css | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index b48d0fb26..8bf8dc8e7 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -385,7 +385,7 @@ if (typeof Survey === "undefined") { Survey.Form.addWidgets(qs); } }, - + addWidgets: function(qs){ hasFile = false; for (var i = 0; i < qs.length; i++) { @@ -394,7 +394,7 @@ if (typeof Survey === "undefined") { // gracefully handle q with no answers continue; } - + var verts = ''; for (var x in q.answers) { if (YAHOO.lang.hasOwnProperty(q.answers, x)) { diff --git a/www/extras/wobject/Survey/surveyedit.css b/www/extras/wobject/Survey/surveyedit.css index 46d4fe0c3..3ff5260fc 100644 --- a/www/extras/wobject/Survey/surveyedit.css +++ b/www/extras/wobject/Survey/surveyedit.css @@ -92,9 +92,11 @@ li.squestion { min-height: 10px; } li.newQuestion { -# background-color: #D1E6EC; -# border:1px solid #7EA6B2; -# cursor: move; +/* + background-color: #D1E6EC; + border:1px solid #7EA6B2; + cursor: move; +*/ padding-left:25px; } @@ -119,15 +121,17 @@ li.sanswer { background-color: #CC6600; border:1px solid #7EA6B2; cursor: move; - padding-left:50px; + padding-left:50px; width:60%; min-height: 10px; } li.newAnswer { -# background-color: #D1E6EC; -# border:1px solid #7EA6B2; - padding-left:50px; -# cursor: move; +/* + background-color: #D1E6EC; + border:1px solid #7EA6B2; + cursor: move; +*/ + padding-left:50px; } #goto-yui-ac { width:15em; From a02cbf95a7c03d452730eef4b47595d597ab9bb1 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 14 Jan 2009 06:15:10 +0000 Subject: [PATCH 18/90] More minor edits from flux branch --- .../Asset/Wobject/Survey/ResponseJSON.pm | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index ab76b27a6..12ab7715f 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -524,7 +524,7 @@ call to goto($target). The expression is a simple subset of the formula language used in spreadsheet programs such as Excel, OpenOffice, Google Docs etc.. -Here is an example using section variables S1 and S2 as jump targets and question variables Q1-3 in the expression. +Here is an example using section variables S1 and S2 as jump targets and question variables Q1-3 in the expression. It jumps to S1 if the user's answer to Q1 has a value of 3, jumps to S2 if Q2 + Q3 < 10, and otherwise doesn't branch at all (the default). S1: Q1 = 3 S2: Q2 + Q3 < 10 @@ -552,11 +552,11 @@ But for now those things can be done manually using the limited subset defined. sub gotoExpression { my $self = shift; my $expression = shift; - + my %responses = ( # questionName => response answer value ); - + # Populate %responses with the user's data.. foreach my $q (@{ $self->returnResponseForReporting() }) { if ($q->{questionName} =~ /\w/) { @@ -564,17 +564,17 @@ sub gotoExpression { $responses{$q->{questionName}} = $value if defined $value; } } - + # Process gotoExpressions one after the other (first one that's true wins) foreach my $line (split '\n', $expression) { my $processed = $self->processGotoExpression($line, \%responses); - + next unless $processed; - + # (ab)use perl's eval to evaluate the processed expression my $result = eval "$processed->{expression}"; $self->warn($@) if $@; - + if ($result) { $self->debug("Truthy, goto [$processed->{target}]"); $self->goto($processed->{target}); @@ -590,7 +590,7 @@ sub gotoExpression { =head2 processGotoExpression ( $expression, $responses) Parses a single gotoExpression. Returns undef if processing fails, or the following hashref -if things work out well: +if things work out well: { target => $target, expression => $expression } =head3 $expression @@ -605,14 +605,14 @@ Hashref that maps questionNames to response values Uses the following simple strategy: -First, parse the expression as: +First, parse the expression as: target: expression Replace each questionName with its response value (from the $responses hashref) Massage the expression into valid perl -Check that only valid tokens remain. This last step ensures that any invalid questionNames in +Check that only valid tokens remain. This last step ensures that any invalid questionNames in the expression generate an error because our list of valid tokens doesn't include a-z =cut @@ -624,9 +624,9 @@ sub processGotoExpression { $self->debug("Processing gotoExpression: $expression"); - # Valid gotoExpression tokens are.. + # Valid gotoExpression tokens are.. my $tokens = qr{\s|[-0-9=!<>+*/.()]}; - + my ( $target, $rest ) = $expression =~ /\s* ([^:]+?) \s* : \s* (.*)/x; $self->debug("Parsed as Target: [$target], Expression: [$rest]"); @@ -653,9 +653,9 @@ sub processGotoExpression { $self->warn("Contains invalid tokens: $rest"); return; } - + $self->debug("Processed as: $rest"); - + return { target => $target, expression => $rest, @@ -854,7 +854,7 @@ Logs an error to the webgui log file, using the session logger. =cut sub log { - my ( $self, $message) = @_; + my ( $self, $message ) = @_; if ( defined $self->{log} ) { $self->{log}->debug($message); } From d5f6d8603e1a6a3211b0aa6eebf5d09b6ac5b1f4 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 15 Jan 2009 09:12:06 +0000 Subject: [PATCH 19/90] Fixed a few js memory leaks in EditSurvey page (SimpleEditor appears to have a memory leak of its own which we either need to get fixed or work-around). --- www/extras/wobject/Survey/editsurvey.js | 44 +++++++++----- .../wobject/Survey/editsurvey/object.js | 58 +++++++++++++------ 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index eb0cee1cf..2bb0d7d61 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -8,6 +8,16 @@ Survey.Data = (function(){ var lastDataSet = {}; var focus; var lastId = -1; + + // Keep references to widgets here so that we can destory any instances before + // creating new ones (to avoid memory leaks) + var autoComplete; + var sButton, qButton, aButton; + + function purgeNode(node) { + YAHOO.util.Event.purgeElement(node, true); + document.getElementById(node).innerHTML = ''; + } return { dragDrop: function(did){ @@ -57,6 +67,11 @@ Survey.Data = (function(){ else { lastId = d.address; } + + // First purge any event handlers bound to sections node.. + YAHOO.util.Event.purgeElement('sections', true); + + // Now we can re-write its innerHTML without fear of memory leaks document.getElementById('sections').innerHTML = d.ddhtml; //add event handlers for if a tag is clicked @@ -68,28 +83,25 @@ Survey.Data = (function(){ } } - //add the add object buttons - // if(d.buttons['section']){ - document.getElementById('addSection').innerHTML = ''; - document.getElementById('addQuestion').innerHTML = ''; - document.getElementById('addAnswer').innerHTML = ''; - var sButton = new YAHOO.widget.Button({ + sButton && sButton.destroy(); + sButton = new YAHOO.widget.Button({ label: "Add Section", id: "addsection", container: "addSection" }); sButton.on("click", this.addSection); - // } - // if(d.buttons['question']){ - var qButton = new YAHOO.widget.Button({ + + qButton && qButton.destroy(); + qButton = new YAHOO.widget.Button({ label: "Add Question", id: "addquestion", container: "addQuestion" }); qButton.on("click", this.addQuestion, d.buttons.question); - // } + if (d.buttons.answer) { - var aButton = new YAHOO.widget.Button({ + aButton && aButton.destroy(); + aButton = new YAHOO.widget.Button({ label: "Add Answer", id: "addanswer", container: "addAnswer" @@ -102,12 +114,16 @@ Survey.Data = (function(){ // build the goto auto-complete widget if (d.gotoTargets && document.getElementById('goto')) { - var ds = new YAHOO.util.LocalDataSource(d.gotoTargets); - var ac = new YAHOO.widget.AutoComplete('goto', 'goto-yui-ac-container', ds); + var ds = new YAHOO.util.LocalDataSource(d.gotoTargets); + autoComplete = new YAHOO.widget.AutoComplete('goto', 'goto-yui-ac-container', ds); } } else { - document.getElementById('edit').innerHTML = ""; + Survey.ObjectTemplate.unloadObject(); + if (autoComplete) { + autoComplete.destroy(); + autoComplete = null; + } } lastDataSet = d; }, diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 75f501bf7..a6fc1a440 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -6,15 +6,32 @@ if (typeof Survey === "undefined") { Survey.ObjectTemplate = (function(){ - var editor; + // Keep references to widgets here so that we can destory any instances before + // creating new ones (to avoid memory leaks) var dialog; + var editor; return { + + unloadObject: function(){ + // First destory the editor.. + if (editor) { + editor.destroy(); + editor = null; + } + + // And then the Dialog that contains it. + if (dialog) { + dialog.destroy(); + dialog = null; + } + }, loadObject: function(html, type){ - + // Make sure we purge any event listeners before overwrite innerHTML.. + YAHOO.util.Event.purgeElement('edit', true); document.getElementById('edit').innerHTML = html; - + var btns = [{ text: "Submit", handler: function(){ @@ -39,8 +56,23 @@ Survey.ObjectTemplate = (function(){ document.getElementById('delete').value = 1; this.submit(); } + }, { + text: "Preview", + handler: function(){ + if (type === 'answer') { + alert('Sorry, preview is only supported for Sections and Questions, not Answers'); + } + else { + var msg = 'This will delete any Survey responses you have made under this ' + + 'user account and redirect you to the Take Survey page starting at the selected item. ' + + "\n\nAre you sure you want to continue?"; + if (confirm(msg)) { + window.location.search = 'func=jumpTo;id=' + dialog.getData().id; + } + } + } }]; - + dialog = new YAHOO.widget.Dialog(type, { width: "600px", context: [document.body, 'tr', 'tr'], @@ -48,26 +80,19 @@ Survey.ObjectTemplate = (function(){ constraintoviewport: true, buttons: btns }); - - if (type !== 'answer') { - btns.push({ - text: "Preview", - handler: function(){ - window.location.search = 'func=jumpTo;id=' + dialog.getData().id; - } - }); - } - + dialog.callback = Survey.Comm.callback; dialog.render(); - + var textareaId = type + 'Text'; var textarea = YAHOO.util.Dom.get(textareaId); - + var height = YAHOO.util.Dom.getStyle(textarea, 'height'); if (!height) { height = '300px'; } + + // N.B. SimpleEditor has a memory leak so this eats memory on every instantiation editor = new YAHOO.widget.SimpleEditor(textareaId, { height: height, width: '100%', @@ -83,4 +108,3 @@ Survey.ObjectTemplate = (function(){ } }; })(); - From 723d39df79ebefe4598e31cbc07bb77446a910be Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 15 Jan 2009 09:18:01 +0000 Subject: [PATCH 20/90] Removed unused function from editsurvey.js --- www/extras/wobject/Survey/editsurvey.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/extras/wobject/Survey/editsurvey.js b/www/extras/wobject/Survey/editsurvey.js index 2bb0d7d61..efb6df3ae 100644 --- a/www/extras/wobject/Survey/editsurvey.js +++ b/www/extras/wobject/Survey/editsurvey.js @@ -14,11 +14,6 @@ Survey.Data = (function(){ var autoComplete; var sButton, qButton, aButton; - function purgeNode(node) { - YAHOO.util.Event.purgeElement(node, true); - document.getElementById(node).innerHTML = ''; - } - return { dragDrop: function(did){ From 46cca4fae1c0c49a075daa435e8e472ef5207872 Mon Sep 17 00:00:00 2001 From: Yung Han Khoe Date: Thu, 15 Jan 2009 14:17:40 +0000 Subject: [PATCH 21/90] rfe 9200: Survey Text/TextArea handling --- lib/WebGUI/Asset/Wobject/Survey.pm | 18 ++++++++++-------- .../Asset/Wobject/Survey/ResponseJSON.pm | 2 +- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 4 ++-- root_import_survey.wgpkg | Bin 9123 -> 8744 bytes www/extras/wobject/Survey/administersurvey.js | 3 ++- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 2aa30564b..d4c6c37d6 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -938,16 +938,18 @@ sub prepareShowSurveyTemplate { 'Confidence', 1, 'Effectiveness', 1, 'Concern', 1, 'Risk', 1, 'Threat', 1, 'Security', 1 ); - my %text = ( 'Text', 1, 'Email', 1, 'Phone Number', 1, 'Text Date', 1, 'Currency', 1 ); - my %slider = ( 'Slider', 1, 'Dual Slider - Range', 1, 'Multi Slider - Allocate', 1 ); - my %dateType = ( 'Date', 1, 'Date Range', 1 ); - my %fileUpload = ( 'File Upload', 1 ); - my %hidden = ( 'Hidden', 1 ); + my %textArea = ( 'TextArea', 1 ); + my %text = ( 'Text', 1, 'Email', 1, 'Phone Number', 1, 'Text Date', 1, 'Currency', 1 ); + my %slider = ( 'Slider', 1, 'Dual Slider - Range', 1, 'Multi Slider - Allocate', 1 ); + my %dateType = ( 'Date', 1, 'Date Range', 1 ); + my %fileUpload = ( 'File Upload', 1 ); + my %hidden = ( 'Hidden', 1 ); foreach my $q (@$questions) { - if ( $fileUpload{ $$q{'questionType'} } ) { $q->{'fileLoader'} = 1; } - elsif ( $text{ $$q{'questionType'} } ) { $q->{'textType'} = 1; } - elsif ( $hidden{ $$q{'questionType'} } ) { $q->{'hidden'} = 1; } + if ( $fileUpload{ $$q{'questionType'} } ) { $q->{'fileLoader'} = 1; } + elsif ( $text{ $$q{'questionType'} } ) { $q->{'textType'} = 1; } + elsif ( $textArea{ $$q{'questionType'} } ) { $q->{'textAreaType'} = 1; } + elsif ( $hidden{ $$q{'questionType'} } ) { $q->{'hidden'} = 1; } elsif ( $multipleChoice{ $$q{'questionType'} } ) { $q->{'multipleChoice'} = 1; if ( $$q{'maxAnswers'} > 1 ) { diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 12ab7715f..7c16258d1 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -388,7 +388,7 @@ sub recordResponses { 'Yes/No', 1 ); my %sliderTypes = ( 'Dual Slider - Range', 1, 'Multi Slider - Allocate', 1, 'Slider', 1 ); - my %textTypes = ( 'Currency', 'Email', 1, 'Phone Number', 1, 'Text', 1, 'Text Date', 1 ); + my %textTypes = ( 'Currency', 'Email', 1, 'Phone Number', 1, 'Text', 1, 'Text Date', 1 ,'TextArea', 1); my %fileTypes = ( 'File Upload', 1 ); my %dateTypes = ( 'Date', 'Date Range', 1 ); my %hiddenTypes = ( 'Hidden', 1 ); diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 57952ff45..73a3a17f1 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -381,8 +381,8 @@ sub getValidQuestionTypes { 'Likelihood', 'Multi Slider - Allocate', 'Multiple Choice', 'Oppose/Support', 'Party', 'Phone Number', 'Race', 'Risk', 'Satisfaction', 'Scale', 'Security', 'Slider', - 'Text', 'Text Date', 'Threat', 'True/False', - 'Yes/No' + 'Text', 'TextArea', 'Text Date', 'Threat', + 'True/False', 'Yes/No' ); } diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index a2cb1e8245c78bb9b7ec833a334d2270b918dfdc..d663c54c01ce628a203e229c59e086f3d2d5a680 100644 GIT binary patch literal 8744 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 RF`NHi-Yj_5V!=|L^Q?@&8RI&$o>K-~TWiy?EKa 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 diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index 8bf8dc8e7..c16533830 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -36,7 +36,8 @@ if (typeof Survey === "undefined") { 'Email': 1, 'Phone Number': 1, 'Text Date': 1, - 'Currency': 1 + 'Currency': 1, + 'TextArea': 1 }; var slider = { 'Slider': 1, From f4dab790936db96f0e8127abae296ce90a8ccece Mon Sep 17 00:00:00 2001 From: Yung Han Khoe Date: Thu, 15 Jan 2009 15:51:39 +0000 Subject: [PATCH 22/90] Rfe 9201: Make Survey textAreas yui resizable --- root_import_survey.wgpkg | Bin 8744 -> 8968 bytes .../wobject/Survey/editsurvey/object.js | 165 +++++++----------- 2 files changed, 64 insertions(+), 101 deletions(-) diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index d663c54c01ce628a203e229c59e086f3d2d5a680..9567630b4c8baa8368c59c4b40517118775c1bc7 100644 GIT binary patch literal 8968 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 RF`NHi-Yj_5V!=|L^Q?@&8RI&$o>K-~TWiy?EKa z`sb$?-H)BH^K%%#>o11?vu*z+(0{IZ8SI}+u7v!vmAM}D9{~T{IrAk>f2bDywo!j= zxsz${TJHdD@lEJUdvS(>>&59LCx%A>b|v0|g}RHL(W(d@H&ZcLfRC1DbWtx5djbEn z_k~aZ_E2RgF@)fiNU4ZNI1V2a)9xO8o!h4ew{of4{LiK~|MT3n!Hdu)%T$Nubba_e zS0O_}QlwVC5>7(5G>;{5ADn0RX;;D{P>IXx6bD)1V;wHUgv2GjWPa!AAt-d5f3#2) z5@*6H=y$HU7G*-Mvp5s}!avN6pE;1963SqHeKWGco+|rpHafw3mYNnzTca=HxKBPq zE@qH8cIPrRU(;Y%k~e1{F<7F{iZN+tlcZ8$Vd`Z=`UfudjuU87Rw+B%Z`3?7#Pi1~ z!t^0i(lQgcyh%G%Gwf~ch?t5r6NL%Yq!$=jeZC}FyTe;ep!#en&a0Rc|4v|n$gbhe zv-6#cQS4{O>Ne@(0`p-yo_JZwfjBu?Gf5z7t|YNSicWJq@^{5gzz92|uCN>PJZI!zONhZMupC|Y_Cpc_YGIV!I@c1UQpP_{+0`&7 zirYrDhHHsEmT$EG8lX;IIKCXvSz7-hFlVzPz~kQ#%qiL4!kk?ZlD~yHH-Rl}>)1+Q SD}k?D0{;QJmi&wW Date: Fri, 16 Jan 2009 14:23:30 +0000 Subject: [PATCH 23/90] applied changes for rfe 9200 to later version of object.js --- .../wobject/Survey/editsurvey/object.js | 177 ++++++++++++------ 1 file changed, 115 insertions(+), 62 deletions(-) diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 67ff56975..74e0c82f4 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -1,73 +1,126 @@ -if (typeof Survey == "undefined") { + +/*global Survey, YAHOO */ +if (typeof Survey === "undefined") { var Survey = {}; } -Survey.ObjectTemplate = new function(){ +Survey.ObjectTemplate = (function(){ - this.loadObject = function(html,type){ + // Keep references to widgets here so that we can destory any instances before + // creating new ones (to avoid memory leaks) + var dialog; + var editor; - document.getElementById('edit').innerHTML = html; + return { + + unloadObject: function(){ + // First destory the editor.. + if (editor) { + editor.destroy(); + editor = null; + } + + // And then the Dialog that contains it. + if (dialog) { + dialog.destroy(); + dialog = null; + } + }, - var myTextarea; + loadObject: function(html, type){ + // Make sure we purge any event listeners before overwrite innerHTML.. + YAHOO.util.Event.purgeElement('edit', true); + document.getElementById('edit').innerHTML = html; + + var btns = [{ + text: "Submit", + handler: function(){ + editor.saveHTML(); + this.submit(); + }, + isDefault: true + }, { + text: "Copy", + handler: function(){ + document.getElementById('copy').value = 1; + this.submit(); + } + }, { + text: "Cancel", + handler: function(){ + this.cancel(); + } + }, { + text: "Delete", + handler: function(){ + document.getElementById('delete').value = 1; + this.submit(); + } + }, { + text: "Preview", + handler: function(){ + if (type === 'answer') { + alert('Sorry, preview is only supported for Sections and Questions, not Answers'); + } + else { + var msg = 'This will delete any Survey responses you have made under this ' + + 'user account and redirect you to the Take Survey page starting at the selected item. ' + + "\n\nAre you sure you want to continue?"; + if (confirm(msg)) { + window.location.search = 'func=jumpTo;id=' + dialog.getData().id; + } + } + } + }]; + + dialog = new YAHOO.widget.Dialog(type, { + width: "600px", + context: [document.body, 'tr', 'tr'], + visible: false, + constraintoviewport: true, + buttons: btns + }); + + dialog.callback = Survey.Comm.callback; + dialog.render(); - var handleSubmit = function(){ - myTextarea.saveHTML(); - this.submit(); - } + if(type == 'question'){ + var resize = new YAHOO.util.Resize('resize_randomWords_formId'); + resize.on('resize', function(ev) { + YAHOO.util.Dom.setStyle('randomWords_formId', 'width', (ev.width - 6) + "px"); + YAHOO.util.Dom.setStyle('randomWords_formId', 'height', (ev.height - 6) + "px"); + }); + } - var butts = [ - { text:"Submit", handler:handleSubmit, isDefault:true }, - { text:"Copy", handler:function(){document.getElementById('copy').value = 1; this.submit();}}, - { text:"Cancel", handler:function(){this.cancel();}}, - { text:"Delete", handler:function(){document.getElementById('delete').value = 1; this.submit();}} - ]; + if(type == 'answer'){ + var resize = new YAHOO.util.Resize('resize_gotoExpression_formId'); + resize.on('resize', function(ev) { + YAHOO.util.Dom.setStyle('gotoExpression_formId', 'width', (ev.width - 6) + "px"); + YAHOO.util.Dom.setStyle('gotoExpression_formId', 'height', (ev.height - 6) + "px"); + }); + } + + var textareaId = type + 'Text'; + var textarea = YAHOO.util.Dom.get(textareaId); + + var height = YAHOO.util.Dom.getStyle(textarea, 'height'); + if (!height) { + height = '300px'; + } - var form = new YAHOO.widget.Dialog(type, - { - width : "500px", - fixedcenter : true, - visible : false, - constraintoviewport : true, - buttons : butts - } ); + // N.B. SimpleEditor has a memory leak so this eats memory on every instantiation + editor = new YAHOO.widget.SimpleEditor(textareaId, { + height: height, + width: '100%', + dompath: false //Turns on the bar at the bottom + }); - form.callback = Survey.Comm.callback; - form.render(); - - if(type == 'question'){ - var resize = new YAHOO.util.Resize('resize_randomWords_formId'); - resize.on('resize', function(ev) { - YAHOO.util.Dom.setStyle('randomWords_formId', 'width', (ev.width - 6) + "px"); - YAHOO.util.Dom.setStyle('randomWords_formId', 'height', (ev.height - 6) + "px"); - }); - } - - if(type == 'answer'){ - var resize = new YAHOO.util.Resize('resize_gotoExpression_formId'); - resize.on('resize', function(ev) { - YAHOO.util.Dom.setStyle('gotoExpression_formId', 'width', (ev.width - 6) + "px"); - YAHOO.util.Dom.setStyle('gotoExpression_formId', 'height', (ev.height - 6) + "px"); - }); - } - - var textareaId = type+'Text'; - var textarea = YAHOO.util.Dom.get(textareaId); - var height = YAHOO.util.Dom.getStyle(textarea,'height'); - if (height == ''){ - height = '300px'; - } - var width = YAHOO.util.Dom.getStyle(textarea,'width'); - if (width == ''){ - width = '500px'; - } - myTextarea = new YAHOO.widget.SimpleEditor(textareaId, { - height: height, - width: width, - dompath: false //Turns on the bar at the bottom - }); - myTextarea.render(); - - form.show(); - } -}(); + if (editor.get('toolbar')) { + editor.get('toolbar').titlebar = false; + } + editor.render(); + dialog.show(); + } + }; +})(); From c4540a96eec7157c4c78036e18e6d89c32ba4ca7 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 19 Jan 2009 03:33:07 +0000 Subject: [PATCH 24/90] Replaces to_json and from_json with encode_json/decode_json to properly handle non-ascii user input (e.g. MS Word apostrophe) --- lib/WebGUI/Asset/Wobject/Survey.pm | 10 +++++----- lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm | 4 ++-- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index d4c6c37d6..601466171 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -277,7 +277,7 @@ sub www_submitObjectEdit { return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); - # my $ref = @{from_json($self->session->form->process("data"))}; + # my $ref = @{decode_json($self->session->form->process("data"))}; my $responses = $self->session->form->paramsHashRef(); my @address = split /-/, $responses->{id}; @@ -415,7 +415,7 @@ sub www_dragDrop { return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); - my $p = from_json( $self->session->form->process("data") ); + my $p = decode_json( $self->session->form->process("data") ); my @tid = split /-/, $p->{target}->{id}; my @bid = split /-/, $p->{before}->{id}; @@ -572,7 +572,7 @@ sub www_loadSurvey { ,gotoTargets => \@gotoTargets, }; $self->session->http->setMimeType('application/json'); - return to_json($return); + return encode_json($return); } ## end sub www_loadSurvey #------------------------------------------------------------------- @@ -924,7 +924,7 @@ sub surveyEnd { } # $self->session->http->setRedirect($url); - return to_json( { "type", "forward", "url", $url } ); + return encode_json( { "type", "forward", "url", $url } ); } ## end sub surveyEnd #------------------------------------------------------------------- @@ -988,7 +988,7 @@ sub prepareShowSurveyTemplate { my $out = $self->processTemplate( $section, $self->get("surveyQuestionsId") ); $self->session->http->setMimeType('application/json'); - return to_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); + return encode_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); } ## end sub prepareShowSurveyTemplate #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 7c16258d1..9675df200 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -61,7 +61,7 @@ sub new { my $json = shift; my $log = shift; my $survey = shift; - my $temp = from_json($json) if defined $json; + my $temp = decode_json($json) if defined $json; my $self = defined $temp ? $temp : {}; $self->{survey} = $survey; $self->{log} = $log; @@ -153,7 +153,7 @@ sub freeze { my %temp = %{$self}; delete $temp{log}; delete $temp{survey}; - return to_json( \%temp ); + return encode_json( \%temp ); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 73a3a17f1..ff0a97aac 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -55,7 +55,7 @@ sub new { my $log = shift; my $self = {}; $self->{log} = $log; - my $temp = from_json($json) if defined $json; + my $temp = decode_json($json) if defined $json; $self->{sections} = defined $temp->{sections} ? $temp->{sections} : []; $self->{survey} = defined $temp->{survey} ? $temp->{survey} : {}; bless( $self, $class ); @@ -77,7 +77,7 @@ sub freeze { my %temp; $temp{sections} = $self->{sections}; $temp{survey} = $self->{survey}; - return to_json( \%temp ); + return encode_json( \%temp ); } =head2 newObject ( $address ) From 6598ca885458ca641a7fcfc3e1c937fa3603d9d3 Mon Sep 17 00:00:00 2001 From: Yung Han Khoe Date: Mon, 19 Jan 2009 12:49:20 +0000 Subject: [PATCH 25/90] rfe 9197: Survey timeout handling --- lib/WebGUI/Asset/Wobject/Survey.pm | 43 ++++++++++++++++++------ lib/WebGUI/i18n/English/Asset_Survey.pm | 30 ++++++++++++++++- root_import_survey.wgpkg | Bin 8968 -> 9029 bytes www/extras/wobject/Survey/survey.css | 7 +++- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 601466171..fd78f56a7 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -57,6 +57,17 @@ sub definition { hoverHelp => $i18n->get('timelimit hoverHelp'), label => $i18n->get('timelimit') }, + doAfterTimeLimit => { + fieldType => 'selectBox', + defaultValue => 'exitUrl', + tab => 'properties', + hoverHelp => $i18n->get('do after timelimit hoverHelp'), + label => $i18n->get('do after timelimit label'), + options => { + 'exitUrl' => $i18n->get('exit url label'), + 'restartSurvey' => $i18n->get('restart survey label'), + }, + }, groupToEditSurvey => { fieldType => 'group', defaultValue => 4, @@ -738,6 +749,7 @@ sub www_view { } #------------------------------------------------------------------- + sub www_takeSurvey { my $self = shift; my %var; @@ -839,7 +851,8 @@ sub www_submitQuestions { #finds the questions to display next and builds the data structre to hold them #------------------------------------------------------------------- sub www_loadQuestions { - my $self = shift; + my $self = shift; + my $wasRestarted = shift; if ( !$self->canTakeSurvey() ) { $self->session->log->debug('canTakeSurvey false, surveyEnd'); @@ -867,7 +880,9 @@ sub www_loadQuestions { my $section = $self->response->nextSection(); #return $self->prepareShowSurveyTemplate($section,$questions); - $section->{id} = $self->response->nextSectionId(); + $section->{id} = $self->response->nextSectionId(); + $section->{wasRestarted} = $wasRestarted; + my $text = $self->prepareShowSurveyTemplate( $section, $questions ); return $text; } ## end sub www_loadQuestions @@ -911,18 +926,24 @@ sub surveyEnd { } ); } - if ( $url !~ /\w/ ) { $url = 0; } - if ( $url eq "undefined" ) { $url = 0; } - if ( !$url ) { - $url - = $self->session->db->quickScalar( - "select exitURL from Survey where assetId = ? order by revisionDate desc limit 1", - [ $self->getId() ] ); + if ($self->get('doAfterTimeLimit') eq 'restartSurvey' && $completeCode == 2){ + $self->response->startTime(time()); + undef $self->{response}; + undef $self->{responseId}; + return $self->www_loadQuestions('1'); + }else{ + if ( $url !~ /\w/ ) { $url = 0; } + if ( $url eq "undefined" ) { $url = 0; } if ( !$url ) { - $url = "/"; + $url + = $self->session->db->quickScalar( + "select exitURL from Survey where assetId = ? order by revisionDate desc limit 1", + [ $self->getId() ] ); + if ( !$url ) { + $url = "/"; + } } } - # $self->session->http->setRedirect($url); return encode_json( { "type", "forward", "url", $url } ); } ## end sub surveyEnd diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index 68a7f01a4..f54494f5f 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -247,7 +247,35 @@ our $I18N = { message => q|How many minutes the user has to finish the survey from the moment they start. 0 means unlimited time.|, lastUpdated => 1231193335, }, - + 'do after timelimit label' => { + message => q|Do After Time Limit:|, + lastUpdated => 1224686319, + context => q|label for the 'do after timelimit' field on the Properties tab of the Survey's edit screen.|, + }, + 'do after timelimit hoverHelp' => { + message => q|Select what happens after the time limit for finishing the survey has expired.|, + lastUpdated => 1231193335, + context => q|description of the 'do after timelimit' field on the Properties tab of the Survey's edit +screen|, + }, + 'exit url label' =>{ + message => q|Exit URL|, + lastUpdated => 0, + context => q|Label for the 'exit url' option of the 'do after timelimit' field on the Properties tab of the +Survey's edit screen|, + }, + 'restart survey label' =>{ + message => q|Restart Survey|, + lastUpdated => 0, + context => q|Label for the 'restart survey' option of the 'do after timelimit' field on the Properties tab of the +Survey's edit screen|, + }, + 'restart message' =>{ + message => q|The survey was restarted because the time limit for completing the survey was reached.|, + lastUpdated => 0, + context => q|The message shown to the user taking the survey when the survey is restarted after reaching +the time limit for completing the survey. This message is in the 'take survey' template.|, + }, }; 1; diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index 9567630b4c8baa8368c59c4b40517118775c1bc7..a91d554af199c991c8fefa3f0b3e7e6d82107add 100644 GIT binary patch literal 9029 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 Date: Mon, 19 Jan 2009 12:51:02 +0000 Subject: [PATCH 26/90] rfe 9197: Survey timeout handling --- docs/upgrades/rfe-9197.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/upgrades/rfe-9197.sql diff --git a/docs/upgrades/rfe-9197.sql b/docs/upgrades/rfe-9197.sql new file mode 100644 index 000000000..918cb6393 --- /dev/null +++ b/docs/upgrades/rfe-9197.sql @@ -0,0 +1 @@ +alter table Survey add doAfterTimeLimit char(22); From 6d10570fdb594ee0c6143f6883524b9ed28c1ba7 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 20 Jan 2009 07:34:55 +0000 Subject: [PATCH 27/90] Refactored SurveyJSON's new, freeze and addObject for readability --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 103 +++++++++++------- t/Asset/Wobject/Survey/SurveyJSON.t | 2 +- 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index ff0a97aac..214c0c4a0 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -30,6 +30,8 @@ Asset in WebGUI. use strict; use JSON; +# N.B. We're currently using Storable::dclone instead of Clone::clone +# because Colin uncovered some Clone bugs in Perl 5.10 #use Clone qw/clone/; use Storable qw/dclone/; @@ -39,8 +41,8 @@ Object constructor. =head3 $json -Pass in some JSON to be serialized into a data structure. Useful JSON would -be a hash with "survey" and "sections" keys with appropriate values. +A JSON string used to construct a new Perl object. The JSON string should +contain a hash made up of "survey" and "sections" keys. =head3 $log @@ -53,84 +55,101 @@ sub new { my $class = shift; my $json = shift; my $log = shift; - my $self = {}; - $self->{log} = $log; - my $temp = decode_json($json) if defined $json; - $self->{sections} = defined $temp->{sections} ? $temp->{sections} : []; - $self->{survey} = defined $temp->{survey} ? $temp->{survey} : {}; + + # Create skeleton object.. + my $self = { + log => $log, + sections => [], + survey => {}, + }; + + # Load json object if given.. + if ($json) { + my $decoded_json = decode_json($json); + $self->{sections} = $decoded_json->{sections} if defined $decoded_json->{sections}; + $self->{survey} = $decoded_json->{survey} if defined $decoded_json->{survey}; + } + bless( $self, $class ); + # Initialise the survey data structure if empty.. if ( @{ $self->sections } == 0 ) { $self->newObject( [] ); } return $self; -} ## end sub new +} =head2 freeze -Serializes the survey and sections data into JSON and returns the JSON. +Serialize this Perl object into a JSON string. The serialized object is made up of the survey and sections +components of this object. =cut sub freeze { my $self = shift; - my %temp; - $temp{sections} = $self->{sections}; - $temp{survey} = $self->{survey}; - return encode_json( \%temp ); + return encode_json( + { sections => $self->{sections}, + survey => $self->{survey}, + } + ); } =head2 newObject ( $address ) -Add new, empty elements to the survey data structure. It returns $address, -modified to show what was added. +Add a new, empty Section, Question or Answer to the survey data structure. + +Updates $address to point at the newly added object. Returns $address. =head3 $address -An array ref. The number of elements array set what is added, and -where. +An array ref that serves as a multidimensional index into the section/question/answer +structure. The first element of the array (if present) is the section index. The second +element of the array (if present) is the question index. -This method modifies $address. It also returns $address. +New objects are always added (pushed) onto the end of the list of similar objects at the +given address. + +If the array ref is empty this sub assumes you want to add a new section. -=over 4 +If the array ref contains a single element (a section index), this sub assumes you want to +add a new question to the indexed section. -=item empty - -If the array ref is empty, a new section is added. - -=item 1 element - -If there's just 1 element, then that element is used as an index into -the array of sections, and a new question is added to that section. - -=item 2 elements - -If there are 2 elements, then the first element is an index into -section array, and the second element is an index into the questions -in that section. A new answer is added to the specified question in -the specified section. - -=back +If the array ref contains two elements (a section index and a question index), this sub +assumes you want to add a new answer to the indexed section/question. =cut sub newObject { my $self = shift; my $address = shift; - if ( @$address == 0 ) { + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + if ( $count == 0 ) { + # Add a new section to the end of the list of sections.. push( @{ $self->sections }, $self->newSection() ); + + # Update $address with the index of the newly created section $address->[0] = $#{ $self->sections }; } - elsif ( @$address == 1 ) { + elsif ( $count == 1 ) { + # Add a new question to the end of the list of questions in section located at $address push( @{ $self->questions($address) }, $self->newQuestion($address) ); - $$address[1] = $#{ $self->questions($address) }; + + # Update $address with the index of the newly created question + $address->[1] = $#{ $self->questions($address) }; } - elsif ( @$address == 2 ) { + elsif ( $count == 2 ) { + # Add a new answer to the end of the list of answers in section/question located at $address push( @{ $self->answers($address) }, $self->newAnswer($address) ); - $$address[2] = $#{ $self->answers($address) }; + + # Update $address with the index of the newly created answer + $address->[2] = $#{ $self->answers($address) }; } return $address; -} ## end sub newObject +} #address is the array of objects currently selected in the edit screen #data is the array of hash items for displaying diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index 7208ea8c8..5c6b2dbaf 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -189,7 +189,7 @@ cmp_deeply( lives_ok { my $foo = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( - qq!{ "survey" : "on 16\x{201d} hand-crocheted Cord" }!, + encode_json({survey => "on 16\x{201d}" }), $session->log ); } From fe32551bcaa82fd7da04081fbe0f94af0f4b96e1 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 21 Jan 2009 03:34:22 +0000 Subject: [PATCH 28/90] Added totalSections/Questions/Answers helper subs, refactored getDragDropList to use helper subs, and added extra $address param documentation. --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 178 +++++++++++++----- t/Asset/Wobject/Survey/SurveyJSON.t | 70 ++++++- 2 files changed, 198 insertions(+), 50 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 214c0c4a0..9c25d3d7c 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -25,6 +25,19 @@ serializing and deserializing JSON data, and manages the data for the Survey. This package is not intended to be used by any other Asset in WebGUI. +=head2 Address Parameter + +Many subroutines in this module accept an $address param. This param is an array ref that +serves as a multidimensional index into the section/question/answer structure. + +In general, the first element of the array is the section index, the second element is +the question index, and the third element is the answer index. + +Most subroutines will not expect or require all three elements to be present. + +Some subroutines (such as newObject) will figure out what you want to do based on the +number of elements you provide. + =cut use strict; @@ -104,8 +117,7 @@ Updates $address to point at the newly added object. Returns $address. =head3 $address An array ref that serves as a multidimensional index into the section/question/answer -structure. The first element of the array (if present) is the section index. The second -element of the array (if present) is the question index. +structure. See L<"Address Parameter">. New objects are always added (pushed) onto the end of the list of similar objects at the given address. @@ -151,9 +163,6 @@ sub newObject { return $address; } -#address is the array of objects currently selected in the edit screen -#data is the array of hash items for displaying - =head2 getDragDropList ( $address ) Get a subset of the entire data structure. It will be a list of all sections, along with @@ -187,8 +196,9 @@ section, section, section, question, answer, answer, answer, section, section =head3 $address -An array ref. Sets which question from a section will be listed, along with all -its answers. $address should ALWAYS have two elements. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. Sets which question from a section will be listed, along with all +its answers. Should ALWAYS have two elements. =cut @@ -196,32 +206,32 @@ sub getDragDropList { my $self = shift; my $address = shift; my @data; - for ( my $i = 0; $i <= $#{ $self->sections }; $i++ ) { - push( @data, { text => $self->section( [$i] )->{title}, type => 'section' } ); - if ( $address->[0] == $i ) { + for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { + push( @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' } ); + if ( $address->[0] == $sIndex ) { - for ( my $x = 0; $x <= $#{ $self->questions($address) }; $x++ ) { + for ( my $qIndex = 0; $qIndex < $self->totalQuestions($address); $qIndex++ ) { push( @data, - { text => $self->question( [ $i, $x ] )->{text}, + { text => $self->question( [ $sIndex, $qIndex ] )->{text}, type => 'question' } ); - if ( $address->[1] == $x ) { - for ( my $y = 0; $y <= $#{ $self->answers($address) }; $y++ ) { + if ( $address->[1] == $qIndex ) { + for ( my $aIndex = 0; $aIndex < $self->totalAnswers($address); $aIndex++ ) { push( @data, - { text => $self->answer( [ $i, $x, $y ] )->{text}, + { text => $self->answer( [ $sIndex, $qIndex, $aIndex ] )->{text}, type => 'answer' } ); } } - } ## end for ( my $x = 0; $x <= ... - } ## end if ( $address->[0] == ... - } ## end for ( my $i = 0; $i <= ... + } + } + } return \@data; -} ## end sub getDragDropList +} =head2 getObject ( $address ) @@ -229,7 +239,8 @@ Retrieve objects from the sections data structure by address. =head3 $address -An array ref. The number of elements array set what is fetched. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements array set what is fetched. =over 4 @@ -278,7 +289,8 @@ from it. =head3 $address -An array ref. The number of elements determines whether edit vars are fetched for +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements determines whether edit vars are fetched for sections, questions, or answers. =cut @@ -310,7 +322,8 @@ selected for this section. =head3 $address -An array reference, specifying which question to fetch variables for. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. Specifies which question to fetch variables for. =cut @@ -358,7 +371,8 @@ selected for this question. =head3 $address -An array reference, specifying which question to fetch variables for. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. Specifies which question to fetch variables for. =cut @@ -414,7 +428,8 @@ in a 1-based array (versus the default, perl style, 0-based array). =head3 $address -An array reference, specifying which answer to fetch variables for. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. Specifies which answer to fetch variables for. =cut @@ -435,7 +450,8 @@ return anything significant. =head3 $address -An array ref. The number of elements array set what is updated. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements array set what is updated. =over 4 @@ -524,7 +540,8 @@ objects. =head3 $address -An array ref. The number of elements array set what is added, and +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements array set what is added, and where. =over 4 @@ -576,7 +593,8 @@ index in that array. =head3 $address -An array ref. The number of elements array set what is added, and +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements array set what is added, and where. This method modifies $address. @@ -624,7 +642,8 @@ Delete the structure pointed to by $address. =head3 $address -An array ref. The number of elements array set what is added, and +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The number of elements array set what is added, and where. This method modifies $address if it has 1 or more elements. @@ -753,7 +772,8 @@ Add answers to a question, based on the requested type. =head3 $address -Which question to add answers to. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. Which question to add answers to. =head3 $type @@ -904,7 +924,8 @@ Helper routine for updateQuestionAnswers. Adds an array of answers to a questio =head3 $address -The address of the question to add answers to. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. The address of the question to add answers to. =head3 $answers @@ -951,21 +972,84 @@ sub sections { return $self->{sections}; } +=head2 totalSections + +Returns the total number of Sections + +=cut + +sub totalSections { + my $self = shift; + return scalar @{ $self->sections || [] }; +} + +=head2 totalQuestions ($address) + +Returns the total number of Questions overall, or in the given Section if $address given + +=head3 $address + +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. + +=cut + +sub totalQuestions { + my $self = shift; + my $address = shift; + if ($address) { + return scalar @{ $self->questions($address) || [] }; + } else { + my $count = 0; + for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { + $count += $self->totalQuestions([$sIndex]); + } + return $count; + } +} + +=head2 totalAnswers ($address) + +Returns the total number of Answers overall, or in the given Question if $address given + +=head3 $address + +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. + +=cut + +sub totalAnswers { + my $self = shift; + my $address = shift; + if ($address) { + return scalar @{ $self->answers($address) || [] }; + } else { + my $count = 0; + for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { + for ( my $qIndex = 0; $qIndex < $self->totalQuestions([$sIndex]); $qIndex++ ) { + $count += $self->totalAnswers([$sIndex, $qIndex]); + } + } + return $count; + } +} + =head2 section ($address) Returns a reference to one section. =head3 $address -An array ref. The first element of the array ref is the index of -the section whose questions will be returned. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. =cut sub section { my $self = shift; my $address = shift; - return $self->{sections}->[ $$address[0] ]; + return $self->{sections}->[ $address->[0] ]; } =head2 questions ($address) @@ -974,15 +1058,15 @@ Returns a reference to all the questions from a particular section. =head3 $address -An array ref. The first element of the array ref is the index of -the section whose questions will be returned. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. =cut sub questions { my $self = shift; my $address = shift; - return $self->{sections}->[ $$address[0] ]->{questions}; + return $self->{sections}->[ $address->[0] ]->{questions}; } =head2 question ($address) @@ -991,16 +1075,15 @@ Return a reference to one question from a particular section. =head3 $address -An array ref. The first element of the array ref is the index of -the section. The second element is the index of the question in -that section. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. =cut sub question { my $self = shift; my $address = shift; - return $self->{sections}->[ $$address[0] ]->{questions}->[ $$address[1] ]; + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; } =head2 answers ($address) @@ -1009,17 +1092,15 @@ Return a reference to all answers from a particular question. =head3 $address -An array ref. The first element of the array ref is the index of -the section. The second element is the index of the question in -that section. An array ref of anwers from that question will be -returned. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. =cut sub answers { my $self = shift; my $address = shift; - return $self->{sections}->[ $$address[0] ]->{questions}->[ $$address[1] ]->{answers}; + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}; } =head2 answer ($address) @@ -1028,16 +1109,15 @@ Return a reference to one answer from a particular question and section. =head3 $address -An array ref. The first element of the array ref is the index of -the section. The second element is the index of the question in -that section. The third element is the index of the answer. +An array ref that serves as a multidimensional index into the section/question/answer +structure. See L<"Address Parameter">. =cut sub answer { my $self = shift; my $address = shift; - return $self->{sections}->[ $$address[0] ]->{questions}->[ $$address[1] ]->{answers}->[ $$address[2] ]; + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; } =head2 log ($message) diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index 5c6b2dbaf..0be9e9e9d 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -22,7 +22,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 96; +my $tests = 134; plan tests => $tests + 1 + 3; #---------------------------------------------------------------------------- @@ -2003,6 +2003,74 @@ cmp_deeply( 'updateQuestionAnswers: Dual Slider - Range' ); +#################################################### +# +# totalSections +# +#################################################### +{ + my $s = WebGUI::Asset::Wobject::Survey::SurveyJSON->new(); + is($s->totalSections, 1, 'a'); + is($s->totalQuestions, 0, 'a'); + is($s->totalAnswers, 0, 'a'); + + # Add a new section + my $address = $s->newObject([]); + is($s->totalSections, 2, 'Now there are 2 sections'); + is($s->totalQuestions, 0, '..but still no questions'); + is($s->totalAnswers, 0, '..and no answers'); + + # Add a question to first section + $address = $s->newObject([0]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 1, '..and now 1 question'); + is($s->totalQuestions([0]), 1, '..in the intended section'); + is($s->totalAnswers, 0, '..but still no answers'); + + # Add a question to second section + $address = $s->newObject([1]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 2, '..and now 2 questions overall'); + is($s->totalQuestions([0]), 1, '..one in the first section'); + is($s->totalQuestions([1]), 1, '..and one in the second section'); + is($s->totalAnswers, 0, '..but still no answers'); + + # Add another question to second section + $address = $s->newObject([1]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 3, '..and now 3 questions overall'); + is($s->totalQuestions([0]), 1, '..one in the first section'); + is($s->totalQuestions([1]), 2, '..and two in the second section'); + is($s->totalAnswers, 0, '..but still no answers'); + + # Add an answer to second section, first question + $address = $s->newObject([1,0]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 3, '..and 3 questions'); + is($s->totalAnswers, 1, '..and now 1 answer overall'); + is($s->totalAnswers([0,0]), 0, '..0 in first question'); + is($s->totalAnswers([1,0]), 1, '..1 in second question'); + is($s->totalAnswers([1,1]), 0, '..0 in third question'); + + # Add an answer to second section, second question + $address = $s->newObject([1,1]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 3, '..and 3 questions'); + is($s->totalAnswers, 2, '..and now 2 answer overall'); + is($s->totalAnswers([0,0]), 0, '..0 in first question'); + is($s->totalAnswers([1,0]), 1, '..1 in second question'); + is($s->totalAnswers([1,1]), 1, '..1 in third question'); + + # Add a second answer to second section, second question + $address = $s->newObject([1,1]); + is($s->totalSections, 2, 'Still 2 sections'); + is($s->totalQuestions, 3, '..and 3 questions'); + is($s->totalAnswers, 3, '..and now 3 answer overall'); + is($s->totalAnswers([0,0]), 0, '..0 in first question'); + is($s->totalAnswers([1,0]), 1, '..1 in second question'); + is($s->totalAnswers([1,1]), 2, '..2 in third question'); +} + #################################################### # # log From ef7c0cfaa191eeee53833e9340f4f4390db6ac84 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 21 Jan 2009 05:34:23 +0000 Subject: [PATCH 29/90] More SurveJSON refactoring (mostly documentation and simple code changes) --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 9c25d3d7c..2190415e9 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -27,16 +27,22 @@ Asset in WebGUI. =head2 Address Parameter -Many subroutines in this module accept an $address param. This param is an array ref that +Most subroutines in this module accept an $address param. This param is an array ref that serves as a multidimensional index into the section/question/answer structure. In general, the first element of the array is the section index, the second element is -the question index, and the third element is the answer index. +the question index, and the third element is the answer index. E.g. in its most general +form the array looks like: -Most subroutines will not expect or require all three elements to be present. + [section index, question index, answer index] -Some subroutines (such as newObject) will figure out what you want to do based on the -number of elements you provide. +Most subroutines will not expect or require all three elements to be present. Often, the +subroutine will alter its behaviour based on how many elements you provide. Typically, +the subroutine will operate on the most specific element it can based on the amount of +information you provide. For example if you provide two elements, the subroutine will most +likely operate on the question indexed by: + + [section index, question index] =cut @@ -116,19 +122,26 @@ Updates $address to point at the newly added object. Returns $address. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. New objects are always added (pushed) onto the end of the list of similar objects at the +given address. -New objects are always added (pushed) onto the end of the list of similar objects at the -given address. - -If the array ref is empty this sub assumes you want to add a new section. +The number of elements in $address determines the behaviour: -If the array ref contains a single element (a section index), this sub assumes you want to -add a new question to the indexed section. +=over 4 -If the array ref contains two elements (a section index and a question index), this sub -assumes you want to add a new answer to the indexed section/question. +=item * 0 elements + +Add a new section. + +=item * 1 element + +Add a new question to the indexed section. + +=item * 2 elements + +Add a new answer to the indexed question inside the indexed section. + +=back =cut @@ -192,13 +205,12 @@ All answers for the referenced question will also be in the array reference: The sections, question and answer will be in depth-first order: -section, section, section, question, answer, answer, answer, section, section + section, section, section, question, answer, answer, answer, section, section =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. Sets which question from a section will be listed, along with all -its answers. Should ALWAYS have two elements. +See L<"Address Parameter">. Determines which question from a section will be listed, along with all +its answers. Should ALWAYS have two elements since we want to address a question. =cut @@ -239,29 +251,28 @@ Retrieve objects from the sections data structure by address. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements array set what is fetched. +See L<"Address Parameter">. + +The number of elements in $address determines the behaviour: =over 4 -=item empty +=item * 0 elements -If the array ref is empty, nothing is done. +Do Nothing -=item 1 element +=item * 1 element -If there's just 1 element, returns the section with that index. +One element is enough to reference a section. Returns that section. -=item 2 elements +=item * 2 elements -If there are 2 elements, then the first element is an index into -section array, and the second element is an index into the questions -in that section. Returns that question. +Two elements are enough to reference a question inside a section. Returns that question. -=item 3 elements +=item * 3 elements -Three elements are enough to reference an answer, inside of a particular -question in a section. Returns that answer. +Three elements are enough to reference an answer, inside of a particular question in a section. +Returns that answer. =back @@ -269,10 +280,16 @@ question in a section. Returns that answer. sub getObject { my ( $self, $address ) = @_; - if ( @$address == 1 ) { + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + return unless $count; + + if ( $count == 1 ) { return dclone $self->{sections}->[ $address->[0] ]; } - elsif ( @$address == 2 ) { + elsif ( $count == 2 ) { return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; } else { @@ -289,8 +306,7 @@ from it. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements determines whether edit vars are fetched for +See L<"Address Parameter">. The number of elements determines whether edit vars are fetched for sections, questions, or answers. =cut @@ -322,8 +338,7 @@ selected for this section. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. Specifies which question to fetch variables for. +See L<"Address Parameter">. Specifies which question to fetch variables for. =cut @@ -371,8 +386,7 @@ selected for this question. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. Specifies which question to fetch variables for. +See L<"Address Parameter">. Specifies which question to fetch variables for. =cut @@ -428,8 +442,7 @@ in a 1-based array (versus the default, perl style, 0-based array). =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. Specifies which answer to fetch variables for. +See L<"Address Parameter">. Specifies which answer to fetch variables for. =cut @@ -450,8 +463,7 @@ return anything significant. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements array set what is updated. +See L<"Address Parameter">. The number of elements array set what is updated. =over 4 @@ -540,8 +552,7 @@ objects. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements array set what is added, and +See L<"Address Parameter">. The number of elements array set what is added, and where. =over 4 @@ -593,8 +604,7 @@ index in that array. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements array set what is added, and +See L<"Address Parameter">. The number of elements array set what is added, and where. This method modifies $address. @@ -642,8 +652,7 @@ Delete the structure pointed to by $address. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The number of elements array set what is added, and +See L<"Address Parameter">. The number of elements array set what is added, and where. This method modifies $address if it has 1 or more elements. @@ -772,8 +781,7 @@ Add answers to a question, based on the requested type. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. Which question to add answers to. +See L<"Address Parameter">. Which question to add answers to. =head3 $type @@ -924,8 +932,7 @@ Helper routine for updateQuestionAnswers. Adds an array of answers to a questio =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. The address of the question to add answers to. +See L<"Address Parameter">. The address of the question to add answers to. =head3 $answers @@ -989,8 +996,7 @@ Returns the total number of Questions overall, or in the given Section if $addre =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1014,8 +1020,7 @@ Returns the total number of Answers overall, or in the given Question if $addres =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1041,8 +1046,7 @@ Returns a reference to one section. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1058,8 +1062,7 @@ Returns a reference to all the questions from a particular section. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1075,8 +1078,7 @@ Return a reference to one question from a particular section. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1092,8 +1094,7 @@ Return a reference to all answers from a particular question. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut @@ -1109,8 +1110,7 @@ Return a reference to one answer from a particular question and section. =head3 $address -An array ref that serves as a multidimensional index into the section/question/answer -structure. See L<"Address Parameter">. +See L<"Address Parameter">. =cut From 102ec0dd25f658b3c2ea299b9a9b25e5c27e2f92 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 21 Jan 2009 05:34:41 +0000 Subject: [PATCH 30/90] More SurveyJSON refactoring.. --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 280 ++++++++++++------ 1 file changed, 192 insertions(+), 88 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 2190415e9..4bad01b9c 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -54,6 +54,9 @@ use JSON; #use Clone qw/clone/; use Storable qw/dclone/; +# The maximum value of questionsPerPage is currently hardcoded here +my $MAX_QUESTIONS_PER_PAGE = 20; + =head2 new ( $json, $log ) Object constructor. @@ -92,7 +95,7 @@ sub new { bless( $self, $class ); # Initialise the survey data structure if empty.. - if ( @{ $self->sections } == 0 ) { + if ( $self->totalSections == 0 ) { $self->newObject( [] ); } return $self; @@ -157,21 +160,21 @@ sub newObject { push( @{ $self->sections }, $self->newSection() ); # Update $address with the index of the newly created section - $address->[0] = $#{ $self->sections }; + $address->[0] = $self->totalSections - 1; } elsif ( $count == 1 ) { # Add a new question to the end of the list of questions in section located at $address push( @{ $self->questions($address) }, $self->newQuestion($address) ); # Update $address with the index of the newly created question - $address->[1] = $#{ $self->questions($address) }; + $address->[1] = $self->totalQuestions($address) - 1; } elsif ( $count == 2 ) { # Add a new answer to the end of the list of answers in section/question located at $address push( @{ $self->answers($address) }, $self->newAnswer($address) ); # Update $address with the index of the newly created answer - $address->[2] = $#{ $self->answers($address) }; + $address->[2] = $self->totalAnswers($address) - 1; } return $address; } @@ -220,7 +223,7 @@ sub getDragDropList { my @data; for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { push( @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' } ); - if ( $address->[0] == $sIndex ) { + if ( sIndex($address) == $sIndex ) { for ( my $qIndex = 0; $qIndex < $self->totalQuestions($address); $qIndex++ ) { push( @@ -229,7 +232,7 @@ sub getDragDropList { type => 'question' } ); - if ( $address->[1] == $qIndex ) { + if ( qIndex($address) == $qIndex ) { for ( my $aIndex = 0; $aIndex < $self->totalAnswers($address); $aIndex++ ) { push( @data, @@ -287,14 +290,14 @@ sub getObject { return unless $count; if ( $count == 1 ) { - return dclone $self->{sections}->[ $address->[0] ]; + return dclone $self->{sections}->[ sIndex($address) ]; } elsif ( $count == 2 ) { - return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; + return dclone $self->{sections}->[ sIndex($address) ]->{questions}->[ qIndex($address) ]; } else { - return dclone $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers} - ->[ $address->[2] ]; + return dclone $self->{sections}->[ sIndex($address) ]->{questions}->[ qIndex($address) ]->{answers} + ->[ aIndex($address) ]; } } @@ -314,23 +317,57 @@ sections, questions, or answers. sub getEditVars { my ( $self, $address ) = @_; - if ( @$address == 1 ) { + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + if ( $count == 1 ) { return $self->getSectionEditVars($address); } - elsif ( @$address == 2 ) { + elsif ( $count == 2 ) { return $self->getQuestionEditVars($address); } - elsif ( @$address == 3 ) { + elsif ( $count == 3 ) { return $self->getAnswerEditVars($address); } } +=head2 getGotoTargets + +Generates the list of valid goto targets + +=cut + +sub getGotoTargets { + my $self = shift; + + # Valid goto targets are all of the section variable names.. + my @section_vars = map {$_->{variable}} @{$self->sections}; + + # ..and all of the question variable names.. + my @question_vars = map {$_->{variable}} @{$self->questions}; + + # ..excluding the ones that are empty + return grep {$_ ne ''} (@section_vars, @question_vars); +} + =head2 getSectionEditVars ( $address ) Get a safe copy of the variables for this section, to use for editing -purposes. Adds two variables, id, which is the index of this section, -and displayed_id, which is this question's index in a 1-based array -(versus the default, perl style, 0-based array). +purposes. + +Adds two variables: + +=over 4 + +=item * id + +the index of this section + +=item * displayed_id + +this question's index in a 1-based array (versus the default, perl style, 0-based array) + +=back It removes the questions array ref, and changes questionsPerPage from a single element, into an array of hashrefs, which list the available questions per page and which one is currently @@ -345,40 +382,47 @@ See L<"Address Parameter">. Specifies which question to fetch variables for. sub getSectionEditVars { my $self = shift; my $address = shift; - my $object = $self->section($address); - my %var = %{$object}; - $var{id} = $address->[0]; - $var{displayed_id} = $address->[0] + 1; + my $section = $self->section($address); + my %var = %{$section}; + + # Add the extra fields.. + $var{id} = sIndex($address); + $var{displayed_id} = sIndex($address) + 1; + + # Remove the fields we don't want.. delete $var{questions}; delete $var{questionsPerPage}; - for ( 1 .. 20 ) { - - # if($_ == $self->section($address)->{questionsPerPage}){ - if ( $_ == $object->{questionsPerPage} ) { - push( @{ $var{questionsPerPage} }, { 'index', $_, 'selected', 1 } ); + # Change questionsPerPage from a single element, into an array of hashrefs, which list the + # available questions per page and which one is currently selected for this section.. + for my $index ( 1 .. $MAX_QUESTIONS_PER_PAGE ) { + if ( $index == $section->{questionsPerPage} ) { + push( @{ $var{questionsPerPage} }, { index => $index, selected => 1 } ); } else { - push( @{ $var{questionsPerPage} }, { 'index', $_, 'selected', 0 } ); + push( @{ $var{questionsPerPage} }, { index => $index, selected => 0 } ); } } return \%var; -} ## end sub getSectionEditVars - -sub getGotoTargets { - my $self = shift; - - my @section_vars = map {$_->{variable}} @{$self->sections}; - my @question_vars = map {$_->{variable}} @{$self->questions}; - return grep {$_ ne ''} (@section_vars, @question_vars); } =head2 getQuestionEditVars ( $address ) -Get a safe copy of the variables for this question, to use for editing purposes. Adds -two variables, id, which is the indeces of the question's position in its parent's -section array joined by dashes '-', and displayed_id, which is this question's index -in a 1-based array (versus the default, perl style, 0-based array). +Get a safe copy of the variables for this question, to use for editing purposes. + +Adds two variables: + +=over 4 + +=item * id + +the index of the question's position in its parent's section array joined by dashes '-' + +=item * displayed_id + +this question's index in a 1-based array (versus the default, perl style, 0-based array). + +=back It removes the answers array ref, and changes questionType from a single element, into an array of hashrefs, which list the available question types and which one is currently @@ -393,24 +437,29 @@ See L<"Address Parameter">. Specifies which question to fetch variables for. sub getQuestionEditVars { my $self = shift; my $address = shift; - my $object = $self->question($address); - my %var = %{$object}; - $var{id} = $address->[0] . "-" . $address->[1]; - $var{displayed_id} = $address->[1] + 1; + my $question = $self->question($address); + my %var = %{$question}; + + # Add the extra fields.. + $var{id} = sIndex($address) . "-" . qIndex($address); + $var{displayed_id} = qIndex($address) + 1; + + # Remove the fields we don't want delete $var{answers}; delete $var{questionType}; - my @types = $self->getValidQuestionTypes(); - for (@types) { - if ( $_ eq $object->{questionType} ) { - push( @{ $var{questionType} }, { 'text', $_, 'selected', 1 } ); + # Change questionType from a single element into an array of hashrefs which list the available + # question types and which one is currently selected for this question.. + for ($self->getValidQuestionTypes) { + if ( $_ eq $question->{questionType} ) { + push( @{ $var{questionType} }, { text => $_, selected => 1 } ); } else { - push( @{ $var{questionType} }, { 'text', $_, 'selected', 0 } ); + push( @{ $var{questionType} }, { text => $_, selected => 0 } ); } } return \%var; -} ## end sub getQuestionEditVars +} =head2 getValidQuestionTypes @@ -435,10 +484,21 @@ sub getValidQuestionTypes { =head2 getAnswerEditVars ( $address ) -Get a safe copy of the variables for this answer, to use for editing purposes. Adds -two variables, id, which is the indeces of the answer's position in its parent's question -and section arrays joined by dashes '-', and displayed_id, which is this answer's index -in a 1-based array (versus the default, perl style, 0-based array). +Get a safe copy of the variables for this answer, to use for editing purposes. + +Adds two variables: + +=over 4 + +=item * id + +The index of the answer's position in its parent's question and section arrays joined by dashes '-' + +=item * displayed_id + +This answer's index in a 1-based array (versus the default, perl style, 0-based array). + +=back =head3 $address @@ -451,93 +511,104 @@ sub getAnswerEditVars { my $address = shift; my $object = $self->answer($address); my %var = %{$object}; - $var{id} = $address->[0] . "-" . $address->[1] . "-" . $address->[2]; - $var{displayed_id} = $address->[2] + 1; + + # Add the extra fields.. + $var{id} = sIndex($address) . "-" . qIndex($address) . "-" . aIndex($address); + $var{displayed_id} = aIndex($address) + 1; + return \%var; } -=head2 update ( $address, $object ) +=head2 update ( $address, $properties ) -Update new "objects" into the current data structure, or add new ones. It does not -return anything significant. +Update a section/question/answer with $properties, or add new ones. +Does not return anything significant. =head3 $address -See L<"Address Parameter">. The number of elements array set what is updated. +See L<"Address Parameter">. + +The number of elements in $address determines the behaviour: =over 4 -=item empty +=item * 0 elements -If the array ref is empty, nothing is done. +Do Nothing -=item 1 element +=item * 1 element -If there's just 1 element, then that element is used as an index into -the array of sections, and information from $object is used to replace -the properties of that section. If the select section does not exist, such +Update the addressed section with $properties. If the section does not exist, such as by using an out of bounds array index, then a new section is appended to the list of sections. -=item 2 elements +=item * 2 elements -If there are 2 elements, then the first element is an index into -section array, and the second element is an index into the questions -in that section. +Update the addressed question with $properties. -=item 3 elements +=item * 3 elements -Three elements are enough to reference an answer, for a particular -question in a section. +Update the addressed answer with $properties. =back -=head3 $object +=head3 $properties A perl data structure. Note, that it is not checked for type, so it is -possible to add a "question" object into the list of section objects. -$object should never be a partial object, but contain all properties. +possible to add a "question" object into the list of sections. +$properties should never be a partial object, but contain all properties. =cut sub update { - my ( $self, $address, $ref ) = @_; + my ( $self, $address, $properties ) = @_; my $object; + + # Keep track of whether a new question is created along the way.. my $newQuestion = 0; - if ( @$address == 1 ) { + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + # First retrieve the addressed object, or, if necessary, create it + if ( $count == 1 ) { $object = $self->section($address); if ( !defined $object ) { $object = $self->newSection(); push( @{ $self->sections }, $object ); } } - elsif ( @$address == 2 ) { + elsif ( $count == 2 ) { $object = $self->question($address); if ( !defined $object ) { - my $newQuestion = 1; $object = $self->newQuestion(); + $newQuestion = 1; # make note that a new question was created push( @{ $self->questions($address) }, $object ); } } - elsif ( @$address == 3 ) { + elsif ( $count == 3 ) { $object = $self->answer($address); if ( !defined $object ) { $object = $self->newAnswer(); push( @{ $self->answers($address) }, $object ); } } - if ( @$address == 2 and !$newQuestion ) { - if ( $ref->{questionType} ne $self->question($address)->{questionType} ) { - $self->updateQuestionAnswers( $address, $ref->{questionType} ); + + # $object and $address now refer to the section/question/answer to be updated + + # In the case where we are updating an existing question.. + if ( $count == 2 and !$newQuestion ) { + # We need to update all of the answers to reflect the new questionType + if ( $properties->{questionType} ne $self->question($address)->{questionType} ) { + $self->updateQuestionAnswers( $address, $properties->{questionType} ); } } - for my $key ( keys %$ref ) { - $object->{$key} = $ref->{$key} if ( defined $$ref{$key} ); + + # Update $object with all of the data in $properties + for my $key ( keys %$properties ) { + $object->{$key} = $properties->{$key} if defined $properties->{$key}; } -} ## end sub update - -#determine what to add and add it. -# ref should contain all the information for the new +} =head2 insertObject ( $object, $address ) @@ -1120,6 +1191,39 @@ sub answer { return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; } +=head2 sIndex ($address) + +Convenience sub to extract the section index from a standard $address parameter. See L<"Address Parameter">. + +=cut + +sub sIndex { + my $address = shift; + return $address->[0]; +} + +=head2 qIndex ($address) + +Convenience sub to extract the question index from a standard $address parameter. See L<"Address Parameter">. + +=cut + +sub qIndex { + my $address = shift; + return $address->[1]; +} + +=head2 aIndex ($address) + +Convenience sub to extract the answer index from a standard $address parameter. See L<"Address Parameter">. + +=cut + +sub aIndex { + my $address = shift; + return $address->[2]; +} + =head2 log ($message) Logs an error message using the session logger. From 8d5e6880c01870af40cfe6322754cb420f28cad7 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 21 Jan 2009 05:34:58 +0000 Subject: [PATCH 31/90] More SurveyJSON refactoring (mostly documentation) --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 152 ++++++++++-------- 1 file changed, 81 insertions(+), 71 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 4bad01b9c..49f04dc03 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -176,6 +176,7 @@ sub newObject { # Update $address with the index of the newly created answer $address->[2] = $self->totalAnswers($address) - 1; } + # Return the (modified) $address return $address; } @@ -612,8 +613,8 @@ sub update { =head2 insertObject ( $object, $address ) -Used to move existing objects in the current data structure. It does not -return anything significant. +Rearrange existing objects in the current data structure. +Does not return anything significant. =head3 $object @@ -623,31 +624,27 @@ objects. =head3 $address -See L<"Address Parameter">. The number of elements array set what is added, and -where. +See L<"Address Parameter">. + +The number of elements in $address determines the behaviour: =over 4 -=item empty +=item * 0 elements -If the array ref is empty, nothing is done. +Do Nothing -=item 1 element +=item * 1 element -If there's just 1 element, then that element is used as an index into -the array of sections, and $object is spliced into place right after -that index. +Reposition $object immediately after the indexed section -=item 2 elements +=item * 2 elements -If there are 2 elements, then the first element is an index into -section array, and the second element is an index into the questions -in that section. $object is added right after that question. +Reposition $object immediately after the indexed question -=item 3 elements +=item * 3 elements -Three elements are enough to reference an answer, inside of a particular -question in a section. $object is spliced in right after that answer. +Reposition $object immediately after the indexed answer =back @@ -655,47 +652,49 @@ question in a section. $object is spliced in right after that answer. sub insertObject { my ( $self, $object, $address ) = @_; - if ( @$address == 1 ) { - splice( @{ $self->sections($address) }, $$address[0] + 1, 0, $object ); + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + return unless $count; + + # Use splice to rearrange the relevant array of objects.. + if ( $count == 1 ) { + splice( @{ $self->sections($address) }, sIndex($address) + 1, 0, $object ); } - elsif ( @$address == 2 ) { - splice( @{ $self->questions($address) }, $$address[1] + 1, 0, $object ); + elsif ( $count == 2 ) { + splice( @{ $self->questions($address) }, qIndex($address) + 1, 0, $object ); } - elsif ( @$address == 3 ) { - splice( @{ $self->answers($address) }, $$address[2] + 1, 0, $object ); + elsif ( $count == 3 ) { + splice( @{ $self->answers($address) }, aIndex($address) + 1, 0, $object ); } - } =head2 copy ( $address ) -Duplicate the structure pointed to by $address, and add it to the end of the list of -similar structures. copy returns $address with the last element changed to the highest -index in that array. +Duplicate the indexed section or question, and push the copy onto the end of the +list of existing items. Modifies $address. Returns $address with the last element changed +to the highest index in that array. =head3 $address -See L<"Address Parameter">. The number of elements array set what is added, and -where. +See L<"Address Parameter">. -This method modifies $address. +The number of elements in $address determines the behaviour: =over 4 -=item 1 element +=item * 1 element -If there's just 1 element, then the section with that index is duplicated -at the end of the array of sections. +Duplice the indexed section onto the end of the array of sections. -=item 2 elements +=item * 2 elements -If there are 2 elements, the question in the section that is indexed -will be duplicated and added to the end of the array of questions -in that section. +Duplice the indexed question onto the end of the array of questions. -=item 3 elements, or more +=item * 3 elements, or more -Nothing happens. It is not allowed to duplicate answers. +Nothing happens. It is not allowed to duplicate answers. =back @@ -703,67 +702,78 @@ Nothing happens. It is not allowed to duplicate answers. sub copy { my ( $self, $address ) = @_; - if ( @$address == 1 ) { - my $newSection = dclone $self->section($address); - push( @{ $self->sections }, $newSection ); - $address->[0] = $#{ $self->sections }; - return $address; + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + if ( $count == 1 ) { + # Clone the indexed section onto the end of the list of sections.. + push( @{ $self->sections }, dclone $self->section($address) ); + + # Update $address with the index of the newly created section + $address->[0] = $self->totalSections - 1; } - elsif ( @$address == 2 ) { - my $newQuestion = dclone $self->question($address); - push( @{ $self->questions($address) }, $newQuestion ); - $address->[1] = $#{ $self->questions($address) }; - return $address; + elsif ( $count == 2 ) { + # Clone the indexed question onto the end of the list of questions.. + push( @{ $self->questions($address) }, dclone $self->question($address) ); + + # Update $address with the index of the newly created question + $address->[1] = $self->totalQuestions($address) - 1; } + # Return the (modified) $address + return $address; } =head2 remove ( $address, $movingOverride ) -Delete the structure pointed to by $address. +Delete the section/question/answer indexed by $address. Modifies $address if it has 1 or more elements. =head3 $address -See L<"Address Parameter">. The number of elements array set what is added, and -where. +See L<"Address Parameter">. -This method modifies $address if it has 1 or more elements. +The number of elements in $address determines the behaviour: =over 4 -=item 1 element +=item * 1 element -If there's just 1 element, then the section with that index is removed. Normally, -the first section, index 0, cannot be removed. See $movingOverride below. +Remove the indexed section. Normally, the first section, index 0, cannot be removed. See $movingOverride below. -=item 2 elements +=item * 2 elements -If there are 2 elements, the question in the section is removed. -in that section. +Remove the indexed question =item 3 elements -Removes the answer in the specified question and section. +Remove the indexed answer =back =head3 $movingOverride -If $movingOverride is defined (meaning including 0 and ''), then the first section -is allowed to be removed. +If $movingOverride is defined (meaning including 0 and ''), then the first section is allowed to be removed. =cut sub remove { my ( $self, $address, $movingOverride ) = @_; - if ( @$address == 1 ) { - splice( @{ $self->{sections} }, $$address[0], 1 ) - if ( $$address[0] != 0 or defined $movingOverride ); #can't delete the first section + + # Figure out what to do by counting the number of elements in the $address array ref + my $count = @$address; + + # Use splice to remove the indexed section/question/answer.. + if ( $count == 1 ) { + # Make sure the first section isn't removed unless we REALLY want to + if ( sIndex($address) != 0 || defined $movingOverride ) { + splice( @{ $self->{sections} }, sIndex($address), 1 ); + } } - elsif ( @$address == 2 ) { - splice( @{ $self->questions($address) }, $$address[1], 1 ); + elsif ( $count == 2 ) { + splice( @{ $self->questions($address) }, qIndex($address), 1 ); } - elsif ( @$address == 3 ) { - splice( @{ $self->answers($address) }, $$address[2], 1 ); + elsif ( $count == 3 ) { + splice( @{ $self->answers($address) }, aIndex($address), 1 ); } } @@ -852,7 +862,7 @@ Add answers to a question, based on the requested type. =head3 $address -See L<"Address Parameter">. Which question to add answers to. +See L<"Address Parameter">. Determines question to add answers to. =head3 $type @@ -995,7 +1005,7 @@ sub updateQuestionAnswers { else { push( @{ $question->{answers} }, $self->newAnswer() ); } -} ## end sub updateQuestionAnswers +} =head2 addAnswersToQuestion ($address, $answers, $verbatims) From a2d01c34dab57a9483d8e294f9ca6403ed3a8a37 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 26 Jan 2009 02:20:11 +0000 Subject: [PATCH 32/90] Fixed a bug where newly created questions would not pre-populate answers (caused by my refactoring) --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 49f04dc03..74f5fb52d 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -563,15 +563,15 @@ $properties should never be a partial object, but contain all properties. sub update { my ( $self, $address, $properties ) = @_; - my $object; - + # Keep track of whether a new question is created along the way.. my $newQuestion = 0; - + # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; - + # First retrieve the addressed object, or, if necessary, create it + my $object; if ( $count == 1 ) { $object = $self->section($address); if ( !defined $object ) { @@ -586,6 +586,10 @@ sub update { $newQuestion = 1; # make note that a new question was created push( @{ $self->questions($address) }, $object ); } + # We need to update all of the answers to reflect the new questionType + if ( $properties->{questionType} ne $object->{questionType} ) { + $self->updateQuestionAnswers( $address, $properties->{questionType} ); + } } elsif ( $count == 3 ) { $object = $self->answer($address); @@ -594,17 +598,7 @@ sub update { push( @{ $self->answers($address) }, $object ); } } - - # $object and $address now refer to the section/question/answer to be updated - - # In the case where we are updating an existing question.. - if ( $count == 2 and !$newQuestion ) { - # We need to update all of the answers to reflect the new questionType - if ( $properties->{questionType} ne $self->question($address)->{questionType} ) { - $self->updateQuestionAnswers( $address, $properties->{questionType} ); - } - } - + # Update $object with all of the data in $properties for my $key ( keys %$properties ) { $object->{$key} = $properties->{$key} if defined $properties->{$key}; From c4eb4e3b57aefc6666a486c742d466e6ac09102f Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 26 Jan 2009 02:20:28 +0000 Subject: [PATCH 33/90] Refactored updateQuestionAnswers and addAnswersToQuestion in SurveyJSON --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 114 ++++++++++-------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 74f5fb52d..fa5b7562a 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -852,7 +852,7 @@ sub newAnswer { =head2 updateQuestionAnswers ($address, $type); -Add answers to a question, based on the requested type. +Remove all existing answers and add a default set of answers to a question, based on question type. =head3 $address @@ -860,8 +860,7 @@ See L<"Address Parameter">. Determines question to add answers to. =head3 $type -The question type to use to determine how many and what kind of answers -to add to the question. +The question type determines how many answers to add and what answer text (if any) to use =cut @@ -870,10 +869,21 @@ sub updateQuestionAnswers { my $address = shift; my $type = shift; - my @addy = @{$address}; + # Make a private copy of the $address arrayref that we can use locally + # when updating answer text without causing side-effects for the caller's $address + my @address_copy = @{$address}; + + # Get the indexed question, and remove all of its existing answers my $question = $self->question($address); $question->{answers} = []; + # Add the default set of answers. The question type determines both the number + # of answers added and the answer text to use. When updating answer text + # first update $address_copy to point to the answer + + # TODO: Rather than being hard-coded, these question type/answer bundles should + # be loaded dynamically and customizable by the user (see also getValidQuestionTypes) + if ( $type eq 'Date Range' or $type eq 'Multi Slider - Allocate' or $type eq 'Dual Slider - Range' ) @@ -883,23 +893,23 @@ sub updateQuestionAnswers { } elsif ( $type eq 'Currency' ) { push( @{ $question->{answers} }, $self->newAnswer() ); - $addy[2] = 0; - $self->update( \@addy, { 'text', 'Currency Amount:' } ); + $address_copy[2] = 0; + $self->update( \@address_copy, { 'text', 'Currency Amount:' } ); } elsif ( $type eq 'Text Date' ) { push( @{ $question->{answers} }, $self->newAnswer() ); - $addy[2] = 0; - $self->update( \@addy, { 'text', 'Date:' } ); + $address_copy[2] = 0; + $self->update( \@address_copy, { 'text', 'Date:' } ); } elsif ( $type eq 'Phone Number' ) { push( @{ $question->{answers} }, $self->newAnswer() ); - $addy[2] = 0; - $self->update( \@addy, { 'text', 'Phone Number:' } ); + $address_copy[2] = 0; + $self->update( \@address_copy, { 'text', 'Phone Number:' } ); } elsif ( $type eq 'Email' ) { push( @{ $question->{answers} }, $self->newAnswer() ); - $addy[2] = 0; - $self->update( \@addy, { 'text', 'Email:' } ); + $address_copy[2] = 0; + $self->update( \@address_copy, { 'text', 'Email:' } ); } elsif ( $type eq 'Education' ) { my @ans = ( @@ -912,17 +922,17 @@ sub updateQuestionAnswers { 'Doctorate (of any type)', 'Other degree (verbatim)' ); - $self->addAnswersToQuestion( \@addy, \@ans, { 7, 1 } ); + $self->addAnswersToQuestion( \@address_copy, \@ans, { 7, 1 } ); } elsif ( $type eq 'Party' ) { my @ans = ( 'Democratic party', 'Republican party (or GOP)', 'Independant party', 'Other party (verbatim)' ); - $self->addAnswersToQuestion( \@addy, \@ans, { 3, 1 } ); + $self->addAnswersToQuestion( \@address_copy, \@ans, { 3, 1 } ); } elsif ( $type eq 'Race' ) { my @ans = ( 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', 'Something else (verbatim)' ); - $self->addAnswersToQuestion( \@addy, \@ans, { 5, 1 } ); + $self->addAnswersToQuestion( \@address_copy, \@ans, { 5, 1 } ); } elsif ( $type eq 'Ideology' ) { my @ans = ( @@ -934,67 +944,67 @@ sub updateQuestionAnswers { 'Conservative', 'Strongly conservative' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Security' ) { my @ans = ( 'Not at all secure', '', '', '', '', '', '', '', '', '', 'Extremely secure' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Threat' ) { my @ans = ( 'No threat', '', '', '', '', '', '', '', '', '', 'Extreme threat' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Risk' ) { my @ans = ( 'No risk', '', '', '', '', '', '', '', '', '', 'Extreme risk' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Concern' ) { my @ans = ( 'Not at all concerned', '', '', '', '', '', '', '', '', '', 'Extremely concerned' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Effectiveness' ) { my @ans = ( 'Not at all effective', '', '', '', '', '', '', '', '', '', 'Extremely effective' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Confidence' ) { my @ans = ( 'Not at all confident', '', '', '', '', '', '', '', '', '', 'Extremely confident' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Satisfaction' ) { my @ans = ( 'Not at all satisfied', '', '', '', '', '', '', '', '', '', 'Extremely satisfied' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Certainty' ) { my @ans = ( 'Not at all certain', '', '', '', '', '', '', '', '', '', 'Extremely certain' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Likelihood' ) { my @ans = ( 'Not at all likely', '', '', '', '', '', '', '', '', '', 'Extremely likely' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Importance' ) { my @ans = ( 'Not at all important', '', '', '', '', '', '', '', '', '', 'Extremely important' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Oppose/Support' ) { my @ans = ( 'Strongly oppose', '', '', '', '', '', 'Strongly support' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Agree/Disagree' ) { my @ans = ( 'Strongly disagree', '', '', '', '', '', 'Strongly agree' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'True/False' ) { my @ans = ( 'True', 'False' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Yes/No' ) { my @ans = ( 'Yes', 'No' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Gender' ) { my @ans = ( 'Male', 'Female' ); - $self->addAnswersToQuestion( \@addy, \@ans, {} ); + $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } else { push( @{ $question->{answers} }, $self->newAnswer() ); @@ -1023,25 +1033,30 @@ set to true. =cut sub addAnswersToQuestion { - my $self = shift; - my $addy = shift; - my $ans = shift; - my $verbs = shift; - for ( 0 .. $#$ans ) { - push( @{ $self->question($addy)->{answers} }, $self->newAnswer() ); - $$addy[2] = $_; - if ( exists $$verbs{$_} and $verbs->{$_} ) { - $self->update( $addy, { 'text', $$ans[$_], 'recordedAnswer', $_ + 1, 'verbatim', 1 } ); - } - else { - $self->update( $addy, { 'text', $$ans[$_], 'recordedAnswer', $_ + 1 } ); - } - } -} ## end sub addAnswersToQuestion + my ( $self, $address, $answers, $verbatims ) = @_; -#------------------------------ -#accessors and helpers -#------------------------------ + # Make a private copy of the $address arrayref that we can use locally + # when updating answer text without causing side-effects for the caller's $address + my @address_copy = @{$address}; + + for my $answer_index ( 0 .. $#$answers ) { + + # Add a new answer to question + push( @{ $self->question( \@address_copy )->{answers} }, $self->newAnswer() ); + + # Update address to point at newly created answer (so that we can update it) + $address_copy[2] = $answer_index; + + # Update the answer appropriately + $self->update( + \@address_copy, + { text => $answers->[$answer_index], + recordedAnswer => $answer_index + 1, + verbatim => $verbatims->{$answer_index}, + } + ); + } +} =head2 sections @@ -1244,4 +1259,5 @@ sub log { $self->{log}->error($message); } } + 1; From a1033aeaa2a6888d33deb8091f8a3040f7139d68 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 27 Jan 2009 05:47:36 +0000 Subject: [PATCH 34/90] Replaced all decode/encode_json method calls with to/from_json method calls now that we've licked the Survey encoding bug (wrong mysql db field type) --- lib/WebGUI/Asset/Wobject/Survey.pm | 13 +++++-------- lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm | 4 ++-- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index fd78f56a7..18979ea84 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -249,9 +249,6 @@ Saves the survey collateral to the DB =cut sub survey { return shift->{survey}; } -sub littleBuddy { return shift->{survey}; } -sub allyourbases { return shift->{survey}; } -sub helpmehelpme { return shift->{survey}; } sub saveSurveyJSON { my $self = shift; @@ -288,7 +285,7 @@ sub www_submitObjectEdit { return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); - # my $ref = @{decode_json($self->session->form->process("data"))}; + # my $ref = @{from_json($self->session->form->process("data"))}; my $responses = $self->session->form->paramsHashRef(); my @address = split /-/, $responses->{id}; @@ -426,7 +423,7 @@ sub www_dragDrop { return $self->session->privilege->insufficient() unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); - my $p = decode_json( $self->session->form->process("data") ); + my $p = from_json( $self->session->form->process("data") ); my @tid = split /-/, $p->{target}->{id}; my @bid = split /-/, $p->{before}->{id}; @@ -583,7 +580,7 @@ sub www_loadSurvey { ,gotoTargets => \@gotoTargets, }; $self->session->http->setMimeType('application/json'); - return encode_json($return); + return to_json($return); } ## end sub www_loadSurvey #------------------------------------------------------------------- @@ -945,7 +942,7 @@ sub surveyEnd { } } # $self->session->http->setRedirect($url); - return encode_json( { "type", "forward", "url", $url } ); + return to_json( { "type", "forward", "url", $url } ); } ## end sub surveyEnd #------------------------------------------------------------------- @@ -1009,7 +1006,7 @@ sub prepareShowSurveyTemplate { my $out = $self->processTemplate( $section, $self->get("surveyQuestionsId") ); $self->session->http->setMimeType('application/json'); - return encode_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); + return to_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); } ## end sub prepareShowSurveyTemplate #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 9675df200..7c16258d1 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -61,7 +61,7 @@ sub new { my $json = shift; my $log = shift; my $survey = shift; - my $temp = decode_json($json) if defined $json; + my $temp = from_json($json) if defined $json; my $self = defined $temp ? $temp : {}; $self->{survey} = $survey; $self->{log} = $log; @@ -153,7 +153,7 @@ sub freeze { my %temp = %{$self}; delete $temp{log}; delete $temp{survey}; - return encode_json( \%temp ); + return to_json( \%temp ); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index fa5b7562a..e001cb7d2 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -87,7 +87,7 @@ sub new { # Load json object if given.. if ($json) { - my $decoded_json = decode_json($json); + my $decoded_json = from_json($json); $self->{sections} = $decoded_json->{sections} if defined $decoded_json->{sections}; $self->{survey} = $decoded_json->{survey} if defined $decoded_json->{survey}; } @@ -110,7 +110,7 @@ components of this object. sub freeze { my $self = shift; - return encode_json( + return to_json( { sections => $self->{sections}, survey => $self->{survey}, } From 76d677ab4fd75743ce56ea16f97d8635303fef3d Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:30:48 +0000 Subject: [PATCH 35/90] Updated & cleaned YUI includes in Default Survey Edit template --- root_import_survey.wgpkg | Bin 9029 -> 9586 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index a91d554af199c991c8fefa3f0b3e7e6d82107add..690eb6c7fc922cd4c032cfba5cc7c1bd8c34d87a 100644 GIT binary patch literal 9586 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 Date: Tue, 3 Feb 2009 08:31:07 +0000 Subject: [PATCH 36/90] Made first argument to SurveyJSON contructor $session for consistency with rest of wg. Also added Params::Validate validation. --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 48 ++++++++----------- t/Asset/Wobject/Survey/ResponseJSON.t | 2 +- t/Asset/Wobject/Survey/SurveyJSON.t | 26 ++++------ 3 files changed, 31 insertions(+), 45 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index e001cb7d2..d57b2ddf5 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -48,6 +48,8 @@ likely operate on the question indexed by: use strict; use JSON; +use Params::Validate qw(:all); +Params::Validate::validation_options( on_fail => sub { WebGUI::Error::InvalidParam->throw( error => shift ) } ); # N.B. We're currently using Storable::dclone instead of Clone::clone # because Colin uncovered some Clone bugs in Perl 5.10 @@ -57,30 +59,28 @@ use Storable qw/dclone/; # The maximum value of questionsPerPage is currently hardcoded here my $MAX_QUESTIONS_PER_PAGE = 20; -=head2 new ( $json, $log ) +=head2 new ( $session, json ) Object constructor. -=head3 $json +=head3 $session -A JSON string used to construct a new Perl object. The JSON string should -contain a hash made up of "survey" and "sections" keys. +WebGUI::Session object -=head3 $log +=head3 $json (optional) -The session logger, from $session->log. The class needs nothing else from the -session object. +A JSON string used to construct a new Perl object. The string should represent +a JSON hash made up of "survey" and "sections" keys. =cut sub new { my $class = shift; - my $json = shift; - my $log = shift; + my ($session, $json) = validate_pos(@_, {isa => 'WebGUI::Session' }, { type => SCALAR, optional => 1}); # Create skeleton object.. my $self = { - log => $log, + session => $session, sections => [], survey => {}, }; @@ -1146,6 +1146,17 @@ sub section { return $self->{sections}->[ $address->[0] ]; } +=head2 session + +Accessor method for the local WebGUI::Session reference + +=cut + +sub session { + my $self = shift; + return $self->{session}; +} + =head2 questions ($address) Returns a reference to all the questions from a particular section. @@ -1243,21 +1254,4 @@ sub aIndex { return $address->[2]; } -=head2 log ($message) - -Logs an error message using the session logger. - -=head3 $message - -The message to log. It will be logged as type "error". - -=cut - -sub log { - my ( $self, $message ) = @_; - if ( defined $self->{log} ) { - $self->{log}->error($message); - } -} - 1; diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 803cac720..36c09a743 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -493,7 +493,7 @@ is($rJSON->questionsAnswered, 0, 'question was all whitespace, not answered'); sub buildSurveyJSON { my $session = shift; - my $sjson = WebGUI::Asset::Wobject::Survey::SurveyJSON->new(undef, $session->log); + my $sjson = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session); ##Build 4 sections. Remembering that one is created by default when you make an empty SurveyJSON object $sjson->newObject([]); $sjson->newObject([]); diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index 0be9e9e9d..d6261337f 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -22,7 +22,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 134; +my $tests = 132; plan tests => $tests + 1 + 3; #---------------------------------------------------------------------------- @@ -126,10 +126,10 @@ skip $tests, "Unable to load SurveyJSON" unless $usedOk; # #################################################### -$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new('{}', $session->log); +$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, '{}'); isa_ok($surveyJSON, 'WebGUI::Asset::Wobject::Survey::SurveyJSON'); -my $sJSON2 = WebGUI::Asset::Wobject::Survey::SurveyJSON->new(undef, $session->log); +my $sJSON2 = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session); isa_ok($sJSON2, 'WebGUI::Asset::Wobject::Survey::SurveyJSON', 'even with absolutely no JSON'); undef $sJSON2; @@ -173,9 +173,8 @@ cmp_deeply( 'new: empty JSON in constructor causes 1 new, default section to be created', ); -$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( +$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, '{ "sections" : [], "survey" : {} }', - $session->log, ); cmp_deeply( @@ -188,16 +187,14 @@ cmp_deeply( lives_ok { - my $foo = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( + my $foo = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, encode_json({survey => "on 16\x{201d}" }), - $session->log ); } 'new handles wide characters'; -$sJSON2 = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( +$sJSON2 = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, '{ "sections" : [ { "type" : "section" } ], "survey" : {} }', - $session->log, ); cmp_deeply( @@ -276,7 +273,7 @@ cmp_deeply( # #################################################### -$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new('{}', $session->log); +$surveyJSON = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, '{}'); { my $section = $surveyJSON->section([0]); $section->{title} = 'Section 0'; @@ -2009,7 +2006,7 @@ cmp_deeply( # #################################################### { - my $s = WebGUI::Asset::Wobject::Survey::SurveyJSON->new(); + my $s = WebGUI::Asset::Wobject::Survey::SurveyJSON->new($session, '{}'); is($s->totalSections, 1, 'a'); is($s->totalQuestions, 0, 'a'); is($s->totalAnswers, 0, 'a'); @@ -2077,12 +2074,7 @@ cmp_deeply( # #################################################### -WebGUI::Test->interceptLogging; - -my $logger = $surveyJSON->log("Everyone in here is innocent"); -is ($WebGUI::Test::logger_warns, undef, 'Did not log a warn'); -is ($WebGUI::Test::logger_info, undef, 'Did not log an info'); -is ($WebGUI::Test::logger_error, "Everyone in here is innocent", 'Logged an error'); +isa_ok($surveyJSON->session, 'WebGUI::Session', 'session() accessor works'); } From 8833459c749bfe215741b1ec106b25835c084935 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:31:24 +0000 Subject: [PATCH 37/90] Added param validation to all SurveyJSON.pm methods. --- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index d57b2ddf5..1c608a4bb 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -150,7 +150,7 @@ Add a new answer to the indexed question inside the indexed section. sub newObject { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -220,7 +220,8 @@ its answers. Should ALWAYS have two elements since we want to address a questio sub getDragDropList { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); + my @data; for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { push( @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' } ); @@ -283,7 +284,8 @@ Returns that answer. =cut sub getObject { - my ( $self, $address ) = @_; + my $self = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -316,7 +318,8 @@ sections, questions, or answers. =cut sub getEditVars { - my ( $self, $address ) = @_; + my $self = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -382,7 +385,8 @@ See L<"Address Parameter">. Specifies which question to fetch variables for. sub getSectionEditVars { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); + my $section = $self->section($address); my %var = %{$section}; @@ -437,7 +441,8 @@ See L<"Address Parameter">. Specifies which question to fetch variables for. sub getQuestionEditVars { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); + my $question = $self->question($address); my %var = %{$question}; @@ -509,7 +514,8 @@ See L<"Address Parameter">. Specifies which answer to fetch variables for. sub getAnswerEditVars { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); + my $object = $self->answer($address); my %var = %{$object}; @@ -555,15 +561,16 @@ Update the addressed answer with $properties. =head3 $properties -A perl data structure. Note, that it is not checked for type, so it is +A perl hash reference. Note, that it is not checked for type, so it is possible to add a "question" object into the list of sections. $properties should never be a partial object, but contain all properties. =cut sub update { - my ( $self, $address, $properties ) = @_; - + my $self = shift; + my ($address, $properties) = validate_pos(@_, { type => ARRAYREF }, {type => HASHREF}); + # Keep track of whether a new question is created along the way.. my $newQuestion = 0; @@ -612,7 +619,7 @@ Does not return anything significant. =head3 $object -A perl data structure. Note, that it is not checked for homegeneity, +A perl hash reference. Note, that it is not checked for homegeneity, so it is possible to add a "question" object into the list of section objects. @@ -645,7 +652,8 @@ Reposition $object immediately after the indexed answer =cut sub insertObject { - my ( $self, $object, $address ) = @_; + my $self = shift; + my ($object, $address) = validate_pos(@_, {type => HASHREF}, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -695,7 +703,8 @@ Nothing happens. It is not allowed to duplicate answers. =cut sub copy { - my ( $self, $address ) = @_; + my $self = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -751,7 +760,8 @@ If $movingOverride is defined (meaning including 0 and ''), then the first secti =cut sub remove { - my ( $self, $address, $movingOverride ) = @_; + my $self = shift; + my ($address, $movingOverride) = validate_pos(@_, { type => ARRAYREF }, 0); # Figure out what to do by counting the number of elements in the $address array ref my $count = @$address; @@ -866,8 +876,7 @@ The question type determines how many answers to add and what answer text (if an sub updateQuestionAnswers { my $self = shift; - my $address = shift; - my $type = shift; + my ($address, $type) = validate_pos(@_, { type => ARRAYREF }, { type => SCALAR, optional => 1}); # Make a private copy of the $address arrayref that we can use locally # when updating answer text without causing side-effects for the caller's $address @@ -1033,7 +1042,9 @@ set to true. =cut sub addAnswersToQuestion { - my ( $self, $address, $answers, $verbatims ) = @_; + my $self = shift; + my ( $address, $answers, $verbatims ) + = validate_pos( @_, { type => ARRAYREF }, { type => ARRAYREF }, { type => HASHREF } ); # Make a private copy of the $address arrayref that we can use locally # when updating answer text without causing side-effects for the caller's $address @@ -1084,15 +1095,16 @@ sub totalSections { Returns the total number of Questions overall, or in the given Section if $address given -=head3 $address +=head3 $address (optional) See L<"Address Parameter">. =cut sub totalQuestions { - my $self = shift; - my $address = shift; + my $self = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF, optional => 1 }); + if ($address) { return scalar @{ $self->questions($address) || [] }; } else { @@ -1108,15 +1120,16 @@ sub totalQuestions { Returns the total number of Answers overall, or in the given Question if $address given -=head3 $address +=head3 $address (optional) See L<"Address Parameter">. =cut sub totalAnswers { - my $self = shift; - my $address = shift; + my $self = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF, optional => 1 }); + if ($address) { return scalar @{ $self->answers($address) || [] }; } else { @@ -1142,7 +1155,8 @@ See L<"Address Parameter">. sub section { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $self->{sections}->[ $address->[0] ]; } @@ -1169,7 +1183,8 @@ See L<"Address Parameter">. sub questions { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $self->{sections}->[ $address->[0] ]->{questions}; } @@ -1185,7 +1200,8 @@ See L<"Address Parameter">. sub question { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; } @@ -1201,7 +1217,8 @@ See L<"Address Parameter">. sub answers { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}; } @@ -1217,7 +1234,8 @@ See L<"Address Parameter">. sub answer { my $self = shift; - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; } @@ -1228,7 +1246,7 @@ Convenience sub to extract the section index from a standard $address parameter. =cut sub sIndex { - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); return $address->[0]; } @@ -1239,7 +1257,7 @@ Convenience sub to extract the question index from a standard $address parameter =cut sub qIndex { - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); return $address->[1]; } @@ -1250,7 +1268,7 @@ Convenience sub to extract the answer index from a standard $address parameter. =cut sub aIndex { - my $address = shift; + my ($address) = validate_pos(@_, { type => ARRAYREF}); return $address->[2]; } From 821635eb71d483548d7c39153a204f3da0fcdd7d Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:31:43 +0000 Subject: [PATCH 38/90] Refactored ResponseJSON and SurveyJSON Added Params::Validate to ResponseJSON.pm Refactored ResponseJSON constructor and re-ordered params for consistency Added new ->session accessor Updates tests Removed unnecessary logging methods Further refactored SurveyJSON, gave private variables underscores, replaced direct hash access with accessors --- lib/WebGUI/Asset/Wobject/Survey.pm | 2 +- .../Asset/Wobject/Survey/ResponseJSON.pm | 130 ++++++++---------- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 42 +++--- t/Asset/Wobject/Survey/ResponseJSON.t | 8 +- t/Asset/Wobject/Survey/SurveyJSON.t | 2 +- 5 files changed, 84 insertions(+), 100 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 18979ea84..92dfd8fa2 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -1039,7 +1039,7 @@ sub loadResponseJSON { if ( !defined $jsonHash ); $self->{response} - = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $jsonHash, $self->session->errorHandler, $self->survey ); + = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $self->survey, $jsonHash ); } ## end sub loadResponseJSON #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 7c16258d1..5f026de59 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -20,60 +20,63 @@ Package WebGUI::Asset::Wobject::Survey::ResponseJSON Helper class for WebGUI::Asset::Wobject::Survey. It manages data from the user, sets the order of questions and answers in the survey, -based on forks, and gotos, and also handles expiring the survey +based on branches, and gotos, and also handles expiring the survey due to time limits. This package is not intended to be used by any other Asset in WebGUI. =cut - use strict; use JSON; -use Data::Dumper; +use Params::Validate qw(:all); +Params::Validate::validation_options( on_fail => sub { WebGUI::Error::InvalidParam->throw( error => shift ) } ); #------------------------------------------------------------------- -=head2 new ( $json, $log, $survey ) +=head2 new ( $survey, $json ) Object constructor. -=head3 $json - -Pass in some JSON to be serialized into a data structure. Useful JSON would -contain a hash with "startTime", "surveyOrder", "responses", "lastReponse" -and "questionsAnswered" keys, with appropriate values. - -=head3 $log - -The session logger, from $session->log. The class needs nothing else from the -session object. - =head3 $survey A WebGUI::Asset::Wobject::Survey::SurveyJSON object that represents the current survey. +=head3 $json + +A JSON string used to construct a new Perl object. The string should represent +a JSON hash made up of "startTime", "surveyOrder", "responses", "lastReponse" +and "questionsAnswered" keys, with appropriate values. + =cut sub new { my $class = shift; - my $json = shift; - my $log = shift; - my $survey = shift; - my $temp = from_json($json) if defined $json; - my $self = defined $temp ? $temp : {}; - $self->{survey} = $survey; - $self->{log} = $log; - $self->{responses} = defined $temp->{responses} ? $temp->{responses} : {}; - $self->{lastResponse} = defined $temp->{lastResponse} ? $temp->{lastResponse} : -1; - $self->{questionsAnswered} = defined $temp->{questionsAnswered} ? $temp->{questionsAnswered} : 0; - $self->{startTime} = defined $temp->{startTime} ? $temp->{startTime} : time(); - #an array of question addresses, with the third member being an array of answers - $self->{surveyOrder} = defined $temp->{surveyOrder} ? $temp->{surveyOrder} : []; - bless( $self, $class ); - return $self; -} ## end sub new + my ($survey, $json) = validate_pos(@_, {isa => 'WebGUI::Asset::Wobject::Survey::SurveyJSON' }, { type => SCALAR, optional => 1}); + + # Load json object if given.. + my $jsonData = $json ? from_json($json) : {}; + + # Create skeleton object.. + my $self = { + # First define core members.. + _survey => $survey, + _session => $survey->session, + + # And now object defaults.. + responses => {}, + lastResponse => -1, + questionsAnswered => 0, + startTime => time(), + surveyOrder => [], + + # And finally, allow jsonData to override defaults and/or add other members + %$jsonData, + }; + + return bless( $self, $class ); +} #---------------------------------------------------------------------------- @@ -125,6 +128,19 @@ sub createSurveyOrder { #------------------------------------------------------------------- +=head2 session + +Accessor method for the local WebGUI::Session reference + +=cut + +sub session { + my $self = shift; + return $self->{_session}; +} + +#------------------------------------------------------------------- + =head2 shuffle ( @array ) Returns the contents of @array in a random order. @@ -151,8 +167,8 @@ Serializes the object to JSON, after deleting the log and survey objects stored sub freeze { my $self = shift; my %temp = %{$self}; - delete $temp{log}; - delete $temp{survey}; + delete $temp{_session}; + delete $temp{_survey}; return to_json( \%temp ); } @@ -573,14 +589,14 @@ sub gotoExpression { # (ab)use perl's eval to evaluate the processed expression my $result = eval "$processed->{expression}"; - $self->warn($@) if $@; + $self->session->log->warn($@) if $@; if ($result) { - $self->debug("Truthy, goto [$processed->{target}]"); + $self->session->log->debug("Truthy, goto [$processed->{target}]"); $self->goto($processed->{target}); return $processed; } else { - $self->debug("Falsy, not branching"); + $self->session->log->debug("Falsy, not branching"); next; } } @@ -622,22 +638,22 @@ sub processGotoExpression { my $expression = shift; my $responses = shift; - $self->debug("Processing gotoExpression: $expression"); + $self->session->log->debug("Processing gotoExpression: $expression"); # Valid gotoExpression tokens are.. my $tokens = qr{\s|[-0-9=!<>+*/.()]}; my ( $target, $rest ) = $expression =~ /\s* ([^:]+?) \s* : \s* (.*)/x; - $self->debug("Parsed as Target: [$target], Expression: [$rest]"); + $self->session->log->debug("Parsed as Target: [$target], Expression: [$rest]"); if ( !defined $target ) { - $self->warn('Target undefined'); + $self->session->log->warn('Target undefined'); return; } if ( !defined $rest || $rest eq '' ) { - $self->warn('Expression undefined'); + $self->session->log->warn('Expression undefined'); return; } @@ -650,11 +666,11 @@ sub processGotoExpression { $rest =~ s/(?])=(?!=)/==/g; if ( $rest !~ /^$tokens+$/ ) { - $self->warn("Contains invalid tokens: $rest"); + $self->session->log->warn("Contains invalid tokens: $rest"); return; } - $self->debug("Processed as: $rest"); + $self->session->log->debug("Processed as: $rest"); return { target => $target, @@ -842,35 +858,7 @@ Note, this is an unsafe reference. sub survey { my $self = shift; - return $self->{survey}; + return $self->{_survey}; } -#------------------------------------------------------------------- - -=head2 log - -Logs an error to the webgui log file, using the session logger. - -=cut - -sub log { - my ( $self, $message ) = @_; - if ( defined $self->{log} ) { - $self->{log}->debug($message); - } -} - -sub debug { - my ( $self, $message) = @_; - if ( defined $self->{log} ) { - $self->{log}->debug($message); - } -} - -sub warn { - my ( $self, $message) = @_; - if ( defined $self->{log} ) { - $self->{log}->warn($message); - } -} 1; diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 1c608a4bb..3153f8835 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -78,20 +78,16 @@ sub new { my $class = shift; my ($session, $json) = validate_pos(@_, {isa => 'WebGUI::Session' }, { type => SCALAR, optional => 1}); + # Load json object if given.. + my $jsonData = $json ? from_json($json) : {}; + # Create skeleton object.. my $self = { - session => $session, - sections => [], - survey => {}, + _session => $session, + _sections => $jsonData->{sections} || [], + _survey => $jsonData->{survey} || {}, }; - # Load json object if given.. - if ($json) { - my $decoded_json = from_json($json); - $self->{sections} = $decoded_json->{sections} if defined $decoded_json->{sections}; - $self->{survey} = $decoded_json->{survey} if defined $decoded_json->{survey}; - } - bless( $self, $class ); # Initialise the survey data structure if empty.. @@ -111,8 +107,8 @@ components of this object. sub freeze { my $self = shift; return to_json( - { sections => $self->{sections}, - survey => $self->{survey}, + { sections => $self->sections, + survey => $self->{_survey}, } ); } @@ -293,13 +289,13 @@ sub getObject { return unless $count; if ( $count == 1 ) { - return dclone $self->{sections}->[ sIndex($address) ]; + return dclone $self->sections->[ sIndex($address) ]; } elsif ( $count == 2 ) { - return dclone $self->{sections}->[ sIndex($address) ]->{questions}->[ qIndex($address) ]; + return dclone $self->sections->[ sIndex($address) ]->{questions}->[ qIndex($address) ]; } else { - return dclone $self->{sections}->[ sIndex($address) ]->{questions}->[ qIndex($address) ]->{answers} + return dclone $self->sections->[ sIndex($address) ]->{questions}->[ qIndex($address) ]->{answers} ->[ aIndex($address) ]; } } @@ -770,7 +766,7 @@ sub remove { if ( $count == 1 ) { # Make sure the first section isn't removed unless we REALLY want to if ( sIndex($address) != 0 || defined $movingOverride ) { - splice( @{ $self->{sections} }, sIndex($address), 1 ); + splice( @{ $self->sections }, sIndex($address), 1 ); } } elsif ( $count == 2 ) { @@ -1077,7 +1073,7 @@ Returns a reference to all the sections in this object. sub sections { my $self = shift; - return $self->{sections}; + return $self->{_sections}; } =head2 totalSections @@ -1157,7 +1153,7 @@ sub section { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF}); - return $self->{sections}->[ $address->[0] ]; + return $self->sections->[ $address->[0] ]; } =head2 session @@ -1168,7 +1164,7 @@ Accessor method for the local WebGUI::Session reference sub session { my $self = shift; - return $self->{session}; + return $self->{_session}; } =head2 questions ($address) @@ -1185,7 +1181,7 @@ sub questions { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF}); - return $self->{sections}->[ $address->[0] ]->{questions}; + return $self->sections->[ $address->[0] ]->{questions}; } =head2 question ($address) @@ -1202,7 +1198,7 @@ sub question { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF}); - return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]; + return $self->sections->[ $address->[0] ]->{questions}->[ $address->[1] ]; } =head2 answers ($address) @@ -1219,7 +1215,7 @@ sub answers { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF}); - return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}; + return $self->sections->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}; } =head2 answer ($address) @@ -1236,7 +1232,7 @@ sub answer { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF}); - return $self->{sections}->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; + return $self->sections->[ $address->[0] ]->{questions}->[ $address->[1] ]->{answers}->[ $address->[2] ]; } =head2 sIndex ($address) diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 36c09a743..5469636bc 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -40,7 +40,7 @@ skip $tests, "Unable to load ResponseJSON" unless $usedOk; #################################################### my $newTime = time(); -$responseJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new('{}', $session->log); +$responseJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), '{}'); isa_ok($responseJSON , 'WebGUI::Asset::Wobject::Survey::ResponseJSON'); is($responseJSON->lastResponse(), -1, 'new: default lastResponse is -1'); @@ -50,7 +50,7 @@ is_deeply( $responseJSON->responses, {}, 'new: by default, responses is an empty is_deeply( $responseJSON->surveyOrder, [], 'new: by default, responses is an empty arrayref'); my $now = time(); -my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(qq!{ "startTime": $now }!, $session->log); +my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), qq!{ "startTime": $now }!); cmp_ok(abs($rJSON->startTime() - $now), '<=', 2, 'new: startTime set using JSON'); #################################################### @@ -85,7 +85,7 @@ ok( ! $rJSON->hasTimedOut(4*60), 'hasTimedOut, limit check'); # #################################################### -$rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(q!{}!, $session->log, buildSurveyJSON($session)); +$rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), q!{}!); $rJSON->createSurveyOrder(); cmp_deeply( @@ -125,7 +125,7 @@ cmp_deeply( { no strict "refs"; no warnings; - my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(q!{}!, $session->log, buildSurveyJSON($session)); + my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), q!{}!); $rJSON->survey->section([0])->{randomizeQuestions} = 0; my $shuffleName = "WebGUI::Asset::Wobject::Survey::ResponseJSON::shuffle"; my $shuffleCalled = 0; diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index d6261337f..78a6370c3 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -2090,7 +2090,7 @@ isa_ok($surveyJSON->session, 'WebGUI::Session', 'session() accessor works'); sub summarizeSectionSkeleton { my ($skeleton) = @_; my $summary = []; - foreach my $section (@{ $skeleton->{sections} }) { + foreach my $section (@{ $skeleton->{_sections} }) { my $summarySection = { title => $section->{title}, questions => [], From 57fb3cb23885c886c24fc8a52f3e09d6f7431730 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:32:06 +0000 Subject: [PATCH 39/90] Refactored SurveyJSON for perlcritic compliance. Simplified some code, refactored out some C-isms. --- .../Asset/Wobject/Survey/ResponseJSON.pm | 12 +- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 256 +++++++++--------- 2 files changed, 137 insertions(+), 131 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 5f026de59..580c0bbd2 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -54,28 +54,28 @@ and "questionsAnswered" keys, with appropriate values. sub new { my $class = shift; my ($survey, $json) = validate_pos(@_, {isa => 'WebGUI::Asset::Wobject::Survey::SurveyJSON' }, { type => SCALAR, optional => 1}); - + # Load json object if given.. my $jsonData = $json ? from_json($json) : {}; - + # Create skeleton object.. my $self = { # First define core members.. _survey => $survey, _session => $survey->session, - + # And now object defaults.. responses => {}, lastResponse => -1, questionsAnswered => 0, startTime => time(), surveyOrder => [], - + # And finally, allow jsonData to override defaults and/or add other members - %$jsonData, + %{$jsonData}, }; - return bless( $self, $class ); + return bless $self, $class; } #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 3153f8835..900838eeb 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -80,7 +80,7 @@ sub new { # Load json object if given.. my $jsonData = $json ? from_json($json) : {}; - + # Create skeleton object.. my $self = { _session => $session, @@ -88,7 +88,7 @@ sub new { _survey => $jsonData->{survey} || {}, }; - bless( $self, $class ); + bless $self, $class; # Initialise the survey data structure if empty.. if ( $self->totalSections == 0 ) { @@ -147,28 +147,28 @@ Add a new answer to the indexed question inside the indexed section. sub newObject { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF }); - + # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; - - if ( $count == 0 ) { + my $count = @{$address}; + + if ( $count == 0 ) { # Add a new section to the end of the list of sections.. - push( @{ $self->sections }, $self->newSection() ); - + push @{ $self->sections }, $self->newSection(); + # Update $address with the index of the newly created section $address->[0] = $self->totalSections - 1; } elsif ( $count == 1 ) { # Add a new question to the end of the list of questions in section located at $address - push( @{ $self->questions($address) }, $self->newQuestion($address) ); - + push @{ $self->questions($address) }, $self->newQuestion($address); + # Update $address with the index of the newly created question $address->[1] = $self->totalQuestions($address) - 1; } elsif ( $count == 2 ) { # Add a new answer to the end of the list of answers in section/question located at $address - push( @{ $self->answers($address) }, $self->newAnswer($address) ); - + push @{ $self->answers($address) }, $self->newAnswer($address); + # Update $address with the index of the newly created answer $address->[2] = $self->totalAnswers($address) - 1; } @@ -219,25 +219,23 @@ sub getDragDropList { my ($address) = validate_pos(@_, { type => ARRAYREF }); my @data; - for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { - push( @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' } ); + for my $sIndex (0 .. $self->totalSections - 1) { + push @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' }; if ( sIndex($address) == $sIndex ) { - for ( my $qIndex = 0; $qIndex < $self->totalQuestions($address); $qIndex++ ) { - push( - @data, + for my $qIndex (0 .. $self->totalQuestions($address) - 1) { + push @data, { text => $self->question( [ $sIndex, $qIndex ] )->{text}, type => 'question' } - ); + ; if ( qIndex($address) == $qIndex ) { - for ( my $aIndex = 0; $aIndex < $self->totalAnswers($address); $aIndex++ ) { - push( - @data, + for my $aIndex (0 .. $self->totalAnswers($address) - 1) { + push @data, { text => $self->answer( [ $sIndex, $qIndex, $aIndex ] )->{text}, type => 'answer' } - ); + ; } } } @@ -282,11 +280,11 @@ Returns that answer. sub getObject { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF }); - + # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; + my $count = @{$address}; - return unless $count; + return if !$count; if ( $count == 1 ) { return dclone $self->sections->[ sIndex($address) ]; @@ -318,7 +316,7 @@ sub getEditVars { my ($address) = validate_pos(@_, { type => ARRAYREF }); # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; + my $count = @{$address}; if ( $count == 1 ) { return $self->getSectionEditVars($address); @@ -342,12 +340,12 @@ sub getGotoTargets { # Valid goto targets are all of the section variable names.. my @section_vars = map {$_->{variable}} @{$self->sections}; - + # ..and all of the question variable names.. my @question_vars = map {$_->{variable}} @{$self->questions}; - + # ..excluding the ones that are empty - return grep {$_ ne ''} (@section_vars, @question_vars); + return grep { $_ ne q{} } (@section_vars, @question_vars); } =head2 getSectionEditVars ( $address ) @@ -385,11 +383,11 @@ sub getSectionEditVars { my $section = $self->section($address); my %var = %{$section}; - + # Add the extra fields.. $var{id} = sIndex($address); $var{displayed_id} = sIndex($address) + 1; - + # Remove the fields we don't want.. delete $var{questions}; delete $var{questionsPerPage}; @@ -397,12 +395,10 @@ sub getSectionEditVars { # Change questionsPerPage from a single element, into an array of hashrefs, which list the # available questions per page and which one is currently selected for this section.. for my $index ( 1 .. $MAX_QUESTIONS_PER_PAGE ) { - if ( $index == $section->{questionsPerPage} ) { - push( @{ $var{questionsPerPage} }, { index => $index, selected => 1 } ); - } - else { - push( @{ $var{questionsPerPage} }, { index => $index, selected => 0 } ); - } + push @{ $var{questionsPerPage} }, { + index => $index, + selected => $index == $section->{questionsPerPage} ? 1 : 0 + }; } return \%var; } @@ -441,24 +437,23 @@ sub getQuestionEditVars { my $question = $self->question($address); my %var = %{$question}; - + # Add the extra fields.. - $var{id} = sIndex($address) . "-" . qIndex($address); + $var{id} = sIndex($address) . q{-} . qIndex($address); $var{displayed_id} = qIndex($address) + 1; - + # Remove the fields we don't want delete $var{answers}; delete $var{questionType}; # Change questionType from a single element into an array of hashrefs which list the available # question types and which one is currently selected for this question.. - for ($self->getValidQuestionTypes) { - if ( $_ eq $question->{questionType} ) { - push( @{ $var{questionType} }, { text => $_, selected => 1 } ); - } - else { - push( @{ $var{questionType} }, { text => $_, selected => 0 } ); - } + + for my $qType ($self->getValidQuestionTypes) { + push @{ $var{questionType} }, { + text => $qType, + selected => $qType eq $question->{questionType} ? 1 : 0 + }; } return \%var; } @@ -466,7 +461,8 @@ sub getQuestionEditVars { =head2 getValidQuestionTypes A convenience method. Returns a list of question types. If you add a question -type to the Survey, you must handle it here, and also in updateQuestionAnswers +type to the Survey, you must handle it here, and also in updateQuestionAnswers() +and administersurvey.js =cut @@ -479,7 +475,7 @@ sub getValidQuestionTypes { 'Likelihood', 'Multi Slider - Allocate', 'Multiple Choice', 'Oppose/Support', 'Party', 'Phone Number', 'Race', 'Risk', 'Satisfaction', 'Scale', 'Security', 'Slider', - 'Text', 'TextArea', 'Text Date', 'Threat', + 'Text', 'TextArea', 'Text Date', 'Threat', 'True/False', 'Yes/No' ); } @@ -514,9 +510,9 @@ sub getAnswerEditVars { my $object = $self->answer($address); my %var = %{$object}; - + # Add the extra fields.. - $var{id} = sIndex($address) . "-" . qIndex($address) . "-" . aIndex($address); + $var{id} = sIndex($address) . q{-} . qIndex($address) . q{-} . aIndex($address); $var{displayed_id} = aIndex($address) + 1; return \%var; @@ -566,12 +562,12 @@ $properties should never be a partial object, but contain all properties. sub update { my $self = shift; my ($address, $properties) = validate_pos(@_, { type => ARRAYREF }, {type => HASHREF}); - + # Keep track of whether a new question is created along the way.. my $newQuestion = 0; # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; + my $count = @{$address}; # First retrieve the addressed object, or, if necessary, create it my $object; @@ -579,7 +575,7 @@ sub update { $object = $self->section($address); if ( !defined $object ) { $object = $self->newSection(); - push( @{ $self->sections }, $object ); + push @{ $self->sections }, $object; } } elsif ( $count == 2 ) { @@ -587,7 +583,7 @@ sub update { if ( !defined $object ) { $object = $self->newQuestion(); $newQuestion = 1; # make note that a new question was created - push( @{ $self->questions($address) }, $object ); + push @{ $self->questions($address) }, $object; } # We need to update all of the answers to reflect the new questionType if ( $properties->{questionType} ne $object->{questionType} ) { @@ -598,14 +594,18 @@ sub update { $object = $self->answer($address); if ( !defined $object ) { $object = $self->newAnswer(); - push( @{ $self->answers($address) }, $object ); + push @{ $self->answers($address) }, $object; } } - # Update $object with all of the data in $properties - for my $key ( keys %$properties ) { - $object->{$key} = $properties->{$key} if defined $properties->{$key}; + # Update $object with all of the data in $properties + while (my ($key, $value) = each %{$properties}) { + if (defined $value) { + $object->{$key} = $value; + } } + + return; } =head2 insertObject ( $object, $address ) @@ -650,22 +650,24 @@ Reposition $object immediately after the indexed answer sub insertObject { my $self = shift; my ($object, $address) = validate_pos(@_, {type => HASHREF}, { type => ARRAYREF }); - + # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; - - return unless $count; + my $count = @{$address}; + return if !$count; + # Use splice to rearrange the relevant array of objects.. if ( $count == 1 ) { - splice( @{ $self->sections($address) }, sIndex($address) + 1, 0, $object ); + splice @{ $self->sections($address) }, sIndex($address) + 1, 0, $object; } elsif ( $count == 2 ) { - splice( @{ $self->questions($address) }, qIndex($address) + 1, 0, $object ); + splice @{ $self->questions($address) }, qIndex($address) + 1, 0, $object; } elsif ( $count == 3 ) { - splice( @{ $self->answers($address) }, aIndex($address) + 1, 0, $object ); + splice @{ $self->answers($address) }, aIndex($address) + 1, 0, $object; } + + return; } =head2 copy ( $address ) @@ -701,21 +703,21 @@ Nothing happens. It is not allowed to duplicate answers. sub copy { my $self = shift; my ($address) = validate_pos(@_, { type => ARRAYREF }); - + # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; + my $count = @{$address}; if ( $count == 1 ) { # Clone the indexed section onto the end of the list of sections.. - push( @{ $self->sections }, dclone $self->section($address) ); - + push @{ $self->sections }, dclone $self->section($address); + # Update $address with the index of the newly created section $address->[0] = $self->totalSections - 1; } elsif ( $count == 2 ) { # Clone the indexed question onto the end of the list of questions.. - push( @{ $self->questions($address) }, dclone $self->question($address) ); - + push @{ $self->questions($address) }, dclone $self->question($address); + # Update $address with the index of the newly created question $address->[1] = $self->totalQuestions($address) - 1; } @@ -758,23 +760,25 @@ If $movingOverride is defined (meaning including 0 and ''), then the first secti sub remove { my $self = shift; my ($address, $movingOverride) = validate_pos(@_, { type => ARRAYREF }, 0); - + # Figure out what to do by counting the number of elements in the $address array ref - my $count = @$address; - + my $count = @{$address}; + # Use splice to remove the indexed section/question/answer.. if ( $count == 1 ) { # Make sure the first section isn't removed unless we REALLY want to if ( sIndex($address) != 0 || defined $movingOverride ) { - splice( @{ $self->sections }, sIndex($address), 1 ); + splice @{ $self->sections }, sIndex($address), 1; } } elsif ( $count == 2 ) { - splice( @{ $self->questions($address) }, qIndex($address), 1 ); + splice @{ $self->questions($address) }, qIndex($address), 1; } elsif ( $count == 3 ) { - splice( @{ $self->answers($address) }, aIndex($address), 1 ); + splice @{ $self->answers($address) }, aIndex($address), 1; } + + return; } =head2 newSection @@ -785,17 +789,17 @@ Returns a reference to a new, empty section. sub newSection { return { - text => '', + text => q{}, title => 'NEW SECTION', ##i18n - variable => '', + variable => q{}, questionsPerPage => 5, questionsOnSectionPage => 1, randomizeQuestions => 0, everyPageTitle => 1, everyPageText => 1, terminal => 0, - terminalUrl => '', - goto => '', + terminalUrl => q{}, + goto => q{}, timeLimit => 0, type => 'section', questions => [], @@ -810,21 +814,19 @@ Returns a reference to a new, empty question. sub newQuestion { return { - text => '', - variable => '', + text => q{}, + variable => q{}, allowComment => 0, commentCols => 10, commentRows => 5, randomizeAnswers => 0, questionType => 'Multiple Choice', - randomWords => '', + randomWords => q{}, verticalDisplay => 0, required => 0, maxAnswers => 1, value => 1, textInButton => 0, -# terminal => 0, -# terminalUrl => '', type => 'question', answers => [], }; @@ -838,20 +840,20 @@ Returns a reference to a new, empty answer. sub newAnswer { return { - text => '', + text => q{}, verbatim => 0, textCols => 10, textRows => 5, - goto => '', - gotoExpression => '', - recordedAnswer => '', + goto => q{}, + gotoExpression => q{}, + recordedAnswer => q{}, isCorrect => 1, min => 1, max => 10, step => 1, value => 1, terminal => 0, - terminalUrl => '', + terminalUrl => q{}, type => 'answer' }; } @@ -877,7 +879,7 @@ sub updateQuestionAnswers { # Make a private copy of the $address arrayref that we can use locally # when updating answer text without causing side-effects for the caller's $address my @address_copy = @{$address}; - + # Get the indexed question, and remove all of its existing answers my $question = $self->question($address); $question->{answers} = []; @@ -885,7 +887,7 @@ sub updateQuestionAnswers { # Add the default set of answers. The question type determines both the number # of answers added and the answer text to use. When updating answer text # first update $address_copy to point to the answer - + # TODO: Rather than being hard-coded, these question type/answer bundles should # be loaded dynamically and customizable by the user (see also getValidQuestionTypes) @@ -893,26 +895,26 @@ sub updateQuestionAnswers { or $type eq 'Multi Slider - Allocate' or $type eq 'Dual Slider - Range' ) { - push( @{ $question->{answers} }, $self->newAnswer() ); - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); + push @{ $question->{answers} }, $self->newAnswer(); } elsif ( $type eq 'Currency' ) { - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); $address_copy[2] = 0; $self->update( \@address_copy, { 'text', 'Currency Amount:' } ); } elsif ( $type eq 'Text Date' ) { - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); $address_copy[2] = 0; $self->update( \@address_copy, { 'text', 'Date:' } ); } elsif ( $type eq 'Phone Number' ) { - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); $address_copy[2] = 0; $self->update( \@address_copy, { 'text', 'Phone Number:' } ); } elsif ( $type eq 'Email' ) { - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); $address_copy[2] = 0; $self->update( \@address_copy, { 'text', 'Email:' } ); } @@ -925,7 +927,7 @@ sub updateQuestionAnswers { 'Some graduate work', 'Master\'s degree', 'Doctorate (of any type)', - 'Other degree (verbatim)' + 'Other degree (verbatim)', ); $self->addAnswersToQuestion( \@address_copy, \@ans, { 7, 1 } ); } @@ -936,7 +938,7 @@ sub updateQuestionAnswers { } elsif ( $type eq 'Race' ) { my @ans = ( 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', - 'Something else (verbatim)' ); + 'Something else (verbatim)', ); $self->addAnswersToQuestion( \@address_copy, \@ans, { 5, 1 } ); } elsif ( $type eq 'Ideology' ) { @@ -947,73 +949,75 @@ sub updateQuestionAnswers { 'Middle of the road', 'Slightly conservative', 'Conservative', - 'Strongly conservative' + 'Strongly conservative', ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Security' ) { - my @ans = ( 'Not at all secure', '', '', '', '', '', '', '', '', '', 'Extremely secure' ); + my @ans = ( 'Not at all secure', (q{}) x 9, 'Extremely secure', ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Threat' ) { - my @ans = ( 'No threat', '', '', '', '', '', '', '', '', '', 'Extreme threat' ); + my @ans = ( 'No threat', (q{}) x 9, 'Extreme threat', ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Risk' ) { - my @ans = ( 'No risk', '', '', '', '', '', '', '', '', '', 'Extreme risk' ); + my @ans = ( 'No risk', (q{}) x 9, 'Extreme risk' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Concern' ) { - my @ans = ( 'Not at all concerned', '', '', '', '', '', '', '', '', '', 'Extremely concerned' ); + my @ans = ( 'Not at all concerned', (q{}) x 9, 'Extremely concerned' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Effectiveness' ) { - my @ans = ( 'Not at all effective', '', '', '', '', '', '', '', '', '', 'Extremely effective' ); + my @ans = ( 'Not at all effective', (q{}) x 9, 'Extremely effective' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Confidence' ) { - my @ans = ( 'Not at all confident', '', '', '', '', '', '', '', '', '', 'Extremely confident' ); + my @ans = ( 'Not at all confident', (q{}) x 9, 'Extremely confident' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Satisfaction' ) { - my @ans = ( 'Not at all satisfied', '', '', '', '', '', '', '', '', '', 'Extremely satisfied' ); + my @ans = ( 'Not at all satisfied', (q{}) x 9, 'Extremely satisfied' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Certainty' ) { - my @ans = ( 'Not at all certain', '', '', '', '', '', '', '', '', '', 'Extremely certain' ); + my @ans = ( 'Not at all certain', (q{}) x 9, 'Extremely certain' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Likelihood' ) { - my @ans = ( 'Not at all likely', '', '', '', '', '', '', '', '', '', 'Extremely likely' ); + my @ans = ( 'Not at all likely', (q{}) x 9, 'Extremely likely' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Importance' ) { - my @ans = ( 'Not at all important', '', '', '', '', '', '', '', '', '', 'Extremely important' ); + my @ans = ( 'Not at all important', (q{}) x 9, 'Extremely important' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Oppose/Support' ) { - my @ans = ( 'Strongly oppose', '', '', '', '', '', 'Strongly support' ); + my @ans = ( 'Strongly oppose', (q{}) x 5, 'Strongly support' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Agree/Disagree' ) { - my @ans = ( 'Strongly disagree', '', '', '', '', '', 'Strongly agree' ); + my @ans = ( 'Strongly disagree', (q{}) x 5, 'Strongly agree' ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'True/False' ) { - my @ans = ( 'True', 'False' ); + my @ans = qw( True False ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Yes/No' ) { - my @ans = ( 'Yes', 'No' ); + my @ans = qw( Yes No ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } elsif ( $type eq 'Gender' ) { - my @ans = ( 'Male', 'Female' ); + my @ans = qw( Male Female ); $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); } else { - push( @{ $question->{answers} }, $self->newAnswer() ); + push @{ $question->{answers} }, $self->newAnswer(); } + + return; } =head2 addAnswersToQuestion ($address, $answers, $verbatims) @@ -1046,10 +1050,10 @@ sub addAnswersToQuestion { # when updating answer text without causing side-effects for the caller's $address my @address_copy = @{$address}; - for my $answer_index ( 0 .. $#$answers ) { + for my $answer_index ( 0 .. $#{$answers} ) { # Add a new answer to question - push( @{ $self->question( \@address_copy )->{answers} }, $self->newAnswer() ); + push @{ $self->question( \@address_copy )->{answers} }, $self->newAnswer(); # Update address to point at newly created answer (so that we can update it) $address_copy[2] = $answer_index; @@ -1063,6 +1067,8 @@ sub addAnswersToQuestion { } ); } + + return; } =head2 sections @@ -1105,7 +1111,7 @@ sub totalQuestions { return scalar @{ $self->questions($address) || [] }; } else { my $count = 0; - for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { + for my $sIndex (0 .. $self->totalSections - 1) { $count += $self->totalQuestions([$sIndex]); } return $count; @@ -1130,8 +1136,8 @@ sub totalAnswers { return scalar @{ $self->answers($address) || [] }; } else { my $count = 0; - for ( my $sIndex = 0; $sIndex < $self->totalSections; $sIndex++ ) { - for ( my $qIndex = 0; $qIndex < $self->totalQuestions([$sIndex]); $qIndex++ ) { + for my $sIndex (0 .. $self->totalSections - 1) { + for my $qIndex (0 .. $self->totalQuestions([$sIndex]) - 1) { $count += $self->totalAnswers([$sIndex, $qIndex]); } } @@ -1240,7 +1246,7 @@ sub answer { Convenience sub to extract the section index from a standard $address parameter. See L<"Address Parameter">. =cut - + sub sIndex { my ($address) = validate_pos(@_, { type => ARRAYREF}); return $address->[0]; From 17dbf7fa662d02099b71ecc13f6817aa50cc9af0 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:32:22 +0000 Subject: [PATCH 40/90] Updated Survey.pm to use the new SurveyJSON contructor params --- lib/WebGUI/Asset/Wobject/Survey.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 92dfd8fa2..87f353814 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -237,7 +237,7 @@ sub loadSurveyJSON { $jsonHash = $self->session->db->quickScalar( "select surveyJSON from Survey where assetId = ?", [ $self->getId ] ) if ( !defined $jsonHash ); - $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $jsonHash, $self->session->errorHandler ); + $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $jsonHash ); } #------------------------------------------------------------------- From 66a2adcbe51e81fbde38e2be5f0abe574ab63f72 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 3 Feb 2009 08:32:40 +0000 Subject: [PATCH 41/90] Moved ResponseJSON data hash to private variable, for security and consistency with SurveyJSON Added some more accessors/mutators, and param validation --- .../Asset/Wobject/Survey/ResponseJSON.pm | 64 ++++++++++++------- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 6 +- t/Asset/Wobject/Survey/ResponseJSON.t | 8 +-- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 580c0bbd2..3ca36d654 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -53,7 +53,7 @@ and "questionsAnswered" keys, with appropriate values. sub new { my $class = shift; - my ($survey, $json) = validate_pos(@_, {isa => 'WebGUI::Asset::Wobject::Survey::SurveyJSON' }, { type => SCALAR, optional => 1}); + my ($survey, $json) = validate_pos(@_, {isa => 'WebGUI::Asset::Wobject::Survey::SurveyJSON' }, { type => SCALAR | UNDEF, optional => 1}); # Load json object if given.. my $jsonData = $json ? from_json($json) : {}; @@ -63,16 +63,18 @@ sub new { # First define core members.. _survey => $survey, _session => $survey->session, - - # And now object defaults.. - responses => {}, - lastResponse => -1, - questionsAnswered => 0, - startTime => time(), - surveyOrder => [], - - # And finally, allow jsonData to override defaults and/or add other members - %{$jsonData}, + _response => { + + # Response hash defaults.. + responses => {}, + lastResponse => -1, + questionsAnswered => 0, + startTime => time(), + surveyOrder => [], + + # And then allow jsonData to override defaults and/or add other members + %{$jsonData}, + }, }; return bless $self, $class; @@ -123,7 +125,7 @@ sub createSurveyOrder { push( @$order, [ $s, $_, \@aorder ] ); } } ## end for ( my $s = 0; $s <= ... - $self->{surveyOrder} = $order; + $self->response->{surveyOrder} = $order; } ## end sub createSurveyOrder #------------------------------------------------------------------- @@ -166,10 +168,7 @@ Serializes the object to JSON, after deleting the log and survey objects stored sub freeze { my $self = shift; - my %temp = %{$self}; - delete $temp{_session}; - delete $temp{_survey}; - return to_json( \%temp ); + return to_json($self->response); } #------------------------------------------------------------------- @@ -213,10 +212,10 @@ sub lastResponse { my $self = shift; my $res = shift; if ( defined $res ) { - $self->{lastResponse} = $res; + $self->response->{lastResponse} = $res; } else { - return $self->{lastResponse}; + return $self->response->{lastResponse}; } } @@ -237,10 +236,10 @@ sub questionsAnswered { my $self = shift; my $answered = shift; if ( defined $answered ) { - $self->{questionsAnswered} += $answered; + $self->response->{questionsAnswered} += $answered; } else { - return $self->{questionsAnswered}; + return $self->response->{questionsAnswered}; } } @@ -261,10 +260,10 @@ sub startTime { my $self = shift; my $newTime = shift; if ( defined $newTime ) { - $self->{startTime} = $newTime; + $self->response->{startTime} = $newTime; } else { - return $self->{startTime}; + return $self->response->{startTime}; } } @@ -287,7 +286,7 @@ If there are no questions, or no addresses, those array elements will not be pre sub surveyOrder { my $self = shift; - return $self->{surveyOrder}; + return $self->response->{surveyOrder}; } #------------------------------------------------------------------- @@ -830,6 +829,17 @@ sub returnResponseForReporting { #Questions only contain the comment and an array of answer Responses. #Answers only contain, entered text, entered verbatim, their index in the Survey Question Answer array, and the assetId to the uploaded file. +=head2 session + +Accessor for the Perl hash containing Response data + +=cut + +sub response { + my $self = shift; + return $self->{_response}; +} + =head2 responses Returns a reference to the actual responses to the survey. A response is for a question and @@ -843,7 +853,13 @@ Note, this is an unsafe reference. sub responses { my $self = shift; - return $self->{responses}; + my $responses = shift; + if ( defined $responses ) { + $self->response->{responses} = $responses; + } + else { + return $self->response->{responses}; + } } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 900838eeb..3d0ff32c3 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -76,7 +76,7 @@ a JSON hash made up of "survey" and "sections" keys. sub new { my $class = shift; - my ($session, $json) = validate_pos(@_, {isa => 'WebGUI::Session' }, { type => SCALAR, optional => 1}); + my ($session, $json) = validate_pos(@_, {isa => 'WebGUI::Session' }, { type => SCALAR | UNDEF, optional => 1}); # Load json object if given.. my $jsonData = $json ? from_json($json) : {}; @@ -874,7 +874,7 @@ The question type determines how many answers to add and what answer text (if an sub updateQuestionAnswers { my $self = shift; - my ($address, $type) = validate_pos(@_, { type => ARRAYREF }, { type => SCALAR, optional => 1}); + my ($address, $type) = validate_pos(@_, { type => ARRAYREF }, { type => SCALAR | UNDEF, optional => 1}); # Make a private copy of the $address arrayref that we can use locally # when updating answer text without causing side-effects for the caller's $address @@ -1185,7 +1185,7 @@ See L<"Address Parameter">. sub questions { my $self = shift; - my ($address) = validate_pos(@_, { type => ARRAYREF}); + my ($address) = validate_pos(@_, { type => ARRAYREF, optional => 1}); return $self->sections->[ $address->[0] ]->{questions}; } diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 5469636bc..0329e2663 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -44,8 +44,8 @@ $responseJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSO isa_ok($responseJSON , 'WebGUI::Asset::Wobject::Survey::ResponseJSON'); is($responseJSON->lastResponse(), -1, 'new: default lastResponse is -1'); -is($responseJSON->{questionsAnswered}, 0, 'new: questionsAnswered is 0 by default'); -cmp_ok((abs$responseJSON->{startTime} - $newTime), '<=', 2, 'new: by default startTime set to time'); +is($responseJSON->questionsAnswered, 0, 'new: questionsAnswered is 0 by default'); +cmp_ok((abs$responseJSON->startTime - $newTime), '<=', 2, 'new: by default startTime set to time'); is_deeply( $responseJSON->responses, {}, 'new: by default, responses is an empty hashref'); is_deeply( $responseJSON->surveyOrder, [], 'new: by default, responses is an empty arrayref'); @@ -390,7 +390,7 @@ is($rJSON->lastResponse(), -1, '.. lastResponse changed to -1 due to goto(s0)'); $rJSON->gotoExpression('s2: s1q0 = 3'); is($rJSON->lastResponse(), 4, '.. lastResponse changed to 4 due to goto(s2)'); -$rJSON->{responses} = {}; +$rJSON->responses({}); $rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); #################################################### @@ -455,7 +455,7 @@ cmp_deeply( $rJSON->survey->question([1,0,0])->{terminal} = 1; $rJSON->survey->question([1,0,0])->{terminalUrl} = 'answer 1-0-0 terminal'; -$rJSON->{responses} = {}; +$rJSON->responses({}); $rJSON->lastResponse(2); $rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); From 6fb325699fa18907e4a87b22261280e357164bc8 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 4 Feb 2009 00:16:26 +0000 Subject: [PATCH 42/90] Refactored createSurveyOrder in ResponseJSON Added lastSectionIndex, lastQuestionIndex, lastAswerIndex convenience methods to SurveyJSON to simplify iterations over Sections, Questions and Answers --- .../Asset/Wobject/Survey/ResponseJSON.pm | 50 ++++++------- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 70 +++++++++++++++---- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 3ca36d654..9f14d8294 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -94,39 +94,41 @@ If questions and/or answers were set to be randomized, it is handled in here. sub createSurveyOrder { my $self = shift; - my $order; - my $qstarting = 0; - for ( my $s = 0; $s <= $#{ $self->survey->sections() }; $s++ ) { + + # Order Questions in each Section + my @surveyOrder; + for my $sIndex ( 0 .. $self->survey->lastSectionIndex ) { - #create question order for section - my @qorder; - if ( $self->survey->section( [$s] )->{randomizeQuestions} ) { - @qorder = shuffle( ( $qstarting .. $#{ $self->survey->questions( [$s] ) } ) ); + # Randomize Questions if required.. + my @qOrder; + if ( $self->survey->section( [$sIndex] )->{randomizeQuestions} ) { + @qOrder = shuffle( 0 .. $self->survey->lastQuestionIndex( [$sIndex] ) ); } else { - @qorder = ( ( $qstarting .. $#{ $self->survey->questions( [$s] ) } ) ); + @qOrder = ( 0 .. $self->survey->lastQuestionIndex( [$sIndex] ) ); } - #if this is an empty section, make sure it is still on the list to be seen - if ( @qorder == 0 ) { - push( @$order, [$s] ); - } - $qstarting = 0; - - #create answer order for question - for (@qorder) { - my @aorder; - if ( $self->survey->question( [ $s, $_ ] )->{randomizeAnswers} ) { - @aorder = shuffle( ( $qstarting .. $#{ $self->survey->question( [ $s, $_ ] )->{answers} } ) ); + # Order Answers in each Question + for my $q (@qOrder) { + + # Randomize Answers if required.. + my @aOrder; + if ( $self->survey->question( [ $sIndex, $q ] )->{randomizeAnswers} ) { + @aOrder = shuffle( 0 .. $self->survey->lastAnswerIndex( [ $sIndex, $q ] ) ); } else { - @aorder = ( ( $qstarting .. $#{ $self->survey->question( [ $s, $_ ] )->{answers} } ) ); + @aOrder = ( 0 .. $self->survey->lastAnswerIndex( [ $sIndex, $q ] ) ); } - push( @$order, [ $s, $_, \@aorder ] ); + push @surveyOrder, [ $sIndex, $q, \@aOrder ]; } - } ## end for ( my $s = 0; $s <= ... - $self->response->{surveyOrder} = $order; -} ## end sub createSurveyOrder + + # If Section had no Questions, make sure it is still added to @surveyOrder + if ( !@qOrder ) { + push @surveyOrder, [$sIndex]; + } + } + $self->response->{surveyOrder} = \@surveyOrder; +} #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 3d0ff32c3..656cc0913 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -156,21 +156,21 @@ sub newObject { push @{ $self->sections }, $self->newSection(); # Update $address with the index of the newly created section - $address->[0] = $self->totalSections - 1; + $address->[0] = $self->lastSectionIndex; } elsif ( $count == 1 ) { # Add a new question to the end of the list of questions in section located at $address push @{ $self->questions($address) }, $self->newQuestion($address); # Update $address with the index of the newly created question - $address->[1] = $self->totalQuestions($address) - 1; + $address->[1] = $self->lastQuestionIndex($address); } elsif ( $count == 2 ) { # Add a new answer to the end of the list of answers in section/question located at $address push @{ $self->answers($address) }, $self->newAnswer($address); # Update $address with the index of the newly created answer - $address->[2] = $self->totalAnswers($address) - 1; + $address->[2] = $self->lastAnswerIndex($address); } # Return the (modified) $address return $address; @@ -219,18 +219,18 @@ sub getDragDropList { my ($address) = validate_pos(@_, { type => ARRAYREF }); my @data; - for my $sIndex (0 .. $self->totalSections - 1) { + for my $sIndex (0 .. $self->lastSectionIndex) { push @data, { text => $self->section( [$sIndex] )->{title}, type => 'section' }; if ( sIndex($address) == $sIndex ) { - for my $qIndex (0 .. $self->totalQuestions($address) - 1) { + for my $qIndex (0 .. $self->lastQuestionIndex($address)) { push @data, { text => $self->question( [ $sIndex, $qIndex ] )->{text}, type => 'question' } ; if ( qIndex($address) == $qIndex ) { - for my $aIndex (0 .. $self->totalAnswers($address) - 1) { + for my $aIndex (0 .. $self->lastAnswerIndex($address)) { push @data, { text => $self->answer( [ $sIndex, $qIndex, $aIndex ] )->{text}, type => 'answer' @@ -712,14 +712,14 @@ sub copy { push @{ $self->sections }, dclone $self->section($address); # Update $address with the index of the newly created section - $address->[0] = $self->totalSections - 1; + $address->[0] = $self->lastSectionIndex; } elsif ( $count == 2 ) { # Clone the indexed question onto the end of the list of questions.. push @{ $self->questions($address) }, dclone $self->question($address); # Update $address with the index of the newly created question - $address->[1] = $self->totalQuestions($address) - 1; + $address->[1] = $self->lastQuestionIndex($address); } # Return the (modified) $address return $address; @@ -1082,6 +1082,52 @@ sub sections { return $self->{_sections}; } +=head2 lastSectionIndex + +Convenience method to return the index of the last Section. Frequently used to +iterate over all Sections. e.g. ( 0 .. lastSectionIndex ) + +=cut + +sub lastSectionIndex { + my $self = shift; + return $self->totalSections(@_) - 1; +} + +=head2 lastQuestionIndex + +Convenience method to return the index of the last Question, overall, or in the +given Section if $address given. Frequently used to +iterate over all Questions. e.g. ( 0 .. lastQuestionIndex ) + +=head3 $address (optional) + +See L<"Address Parameter">. + +=cut + +sub lastQuestionIndex { + my $self = shift; + return $self->totalQuestions(@_) - 1; +} + +=head2 lastQuestionIndex + +Convenience method to return the index of the last Answer, overall, or in the +given Question if $address given. Frequently used to +iterate over all Answers. e.g. ( 0 .. lastAnswerIndex ) + +=head3 $address (optional) + +See L<"Address Parameter">. + +=cut + +sub lastAnswerIndex { + my $self = shift; + return $self->totalAnswers(@_) - 1; +} + =head2 totalSections Returns the total number of Sections @@ -1095,7 +1141,7 @@ sub totalSections { =head2 totalQuestions ($address) -Returns the total number of Questions overall, or in the given Section if $address given +Returns the total number of Questions, overall, or in the given Section if $address given =head3 $address (optional) @@ -1111,7 +1157,7 @@ sub totalQuestions { return scalar @{ $self->questions($address) || [] }; } else { my $count = 0; - for my $sIndex (0 .. $self->totalSections - 1) { + for my $sIndex (0 .. $self->lastSectionIndex) { $count += $self->totalQuestions([$sIndex]); } return $count; @@ -1136,8 +1182,8 @@ sub totalAnswers { return scalar @{ $self->answers($address) || [] }; } else { my $count = 0; - for my $sIndex (0 .. $self->totalSections - 1) { - for my $qIndex (0 .. $self->totalQuestions([$sIndex]) - 1) { + for my $sIndex (0 .. $self->lastSectionIndex) { + for my $qIndex (0 .. $self->lastQuestionIndex([$sIndex])) { $count += $self->totalAnswers([$sIndex, $qIndex]); } } From 477f0141775da94c4d3a7a279a83ff3a6fc5094a Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 4 Feb 2009 00:16:44 +0000 Subject: [PATCH 43/90] Improved createSurveyOrder documentation, and made perlcritic happy --- .../Asset/Wobject/Survey/ResponseJSON.pm | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 9f14d8294..f300a15f3 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -64,14 +64,14 @@ sub new { _survey => $survey, _session => $survey->session, _response => { - + # Response hash defaults.. responses => {}, lastResponse => -1, questionsAnswered => 0, startTime => time(), surveyOrder => [], - + # And then allow jsonData to override defaults and/or add other members %{$jsonData}, }, @@ -82,19 +82,18 @@ sub new { #---------------------------------------------------------------------------- -=head2 createSurveyOrder ( SurveyJSON, [address,address] ) +=head2 createSurveyOrder -This creates the order for the survey which will change after every fork. The survey -order is to precreate random questions and answers, which also leaves a record or what -the user was presented with. Forks are passed in to show where to branch the new order. - -If questions and/or answers were set to be randomized, it is handled in here. +Computes the order of Sections, Questions and Aswers for this Survey. The order is represented as +an array of addresses (see L<"Address Parameter">), and is stored in the surveyOrder property. +Questions and Answers that are set to be randomized are shuffled into a random order. +The survey order leaves a record or what the user was presented with. =cut sub createSurveyOrder { my $self = shift; - + # Order Questions in each Section my @surveyOrder; for my $sIndex ( 0 .. $self->survey->lastSectionIndex ) { @@ -110,7 +109,7 @@ sub createSurveyOrder { # Order Answers in each Question for my $q (@qOrder) { - + # Randomize Answers if required.. my @aOrder; if ( $self->survey->question( [ $sIndex, $q ] )->{randomizeAnswers} ) { @@ -121,13 +120,15 @@ sub createSurveyOrder { } push @surveyOrder, [ $sIndex, $q, \@aOrder ]; } - + # If Section had no Questions, make sure it is still added to @surveyOrder if ( !@qOrder ) { push @surveyOrder, [$sIndex]; } } $self->response->{surveyOrder} = \@surveyOrder; + + return; } #------------------------------------------------------------------- From 669e986189dda9678255e1acba015c26e80c1baa Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 4 Feb 2009 02:05:44 +0000 Subject: [PATCH 44/90] Replaced ResponseJSON's implementation of shuffle with List::Util's shuffle and updated Colin's ninja shuffle tests --- .../Asset/Wobject/Survey/ResponseJSON.pm | 22 ++--------- t/Asset/Wobject/Survey/ResponseJSON.t | 39 +++++++++---------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index f300a15f3..662b865bc 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -30,6 +30,7 @@ This package is not intended to be used by any other Asset in WebGUI. use strict; use JSON; use Params::Validate qw(:all); +use List::Util qw(shuffle); Params::Validate::validation_options( on_fail => sub { WebGUI::Error::InvalidParam->throw( error => shift ) } ); #------------------------------------------------------------------- @@ -101,7 +102,7 @@ sub createSurveyOrder { # Randomize Questions if required.. my @qOrder; if ( $self->survey->section( [$sIndex] )->{randomizeQuestions} ) { - @qOrder = shuffle( 0 .. $self->survey->lastQuestionIndex( [$sIndex] ) ); + @qOrder = shuffle 0 .. $self->survey->lastQuestionIndex( [$sIndex] ); } else { @qOrder = ( 0 .. $self->survey->lastQuestionIndex( [$sIndex] ) ); @@ -113,7 +114,7 @@ sub createSurveyOrder { # Randomize Answers if required.. my @aOrder; if ( $self->survey->question( [ $sIndex, $q ] )->{randomizeAnswers} ) { - @aOrder = shuffle( 0 .. $self->survey->lastAnswerIndex( [ $sIndex, $q ] ) ); + @aOrder = shuffle 0 .. $self->survey->lastAnswerIndex( [ $sIndex, $q ] ); } else { @aOrder = ( 0 .. $self->survey->lastAnswerIndex( [ $sIndex, $q ] ) ); @@ -146,23 +147,6 @@ sub session { #------------------------------------------------------------------- -=head2 shuffle ( @array ) - -Returns the contents of @array in a random order. - -=cut - -sub shuffle { - my @a = splice @_; - for my $i ( 0 .. $#a ) { - my $j = int rand @a; - @a[ $i, $j ] = @a[ $j, $i ]; - } - return @a; -} - -#------------------------------------------------------------------- - =head2 freeze Serializes the object to JSON, after deleting the log and survey objects stored in it. diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 0329e2663..90d600ff4 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -20,7 +20,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 77; +my $tests = 78; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -123,33 +123,30 @@ cmp_deeply( #################################################### { - no strict "refs"; - no warnings; my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), q!{}!); - $rJSON->survey->section([0])->{randomizeQuestions} = 0; - my $shuffleName = "WebGUI::Asset::Wobject::Survey::ResponseJSON::shuffle"; - my $shuffleCalled = 0; - my $shuffleRef = \&$shuffleName; - *$shuffleName = sub { - $shuffleCalled = 1; - goto &$shuffleRef; - }; - $rJSON->createSurveyOrder(); - is($shuffleCalled, 0, 'createSurveyOrder did not call shuffle on a section'); - $shuffleCalled = 0; + $rJSON->survey->section([0])->{randomizeQuestions} = 0; + $rJSON->createSurveyOrder(); + my @question_order = map {$_->[1]} grep {$_->[0] == 0} @{$rJSON->surveyOrder}; + cmp_deeply(\@question_order, [0,1,2], 'createSurveyOrder did not shuffle questions'); + $rJSON->survey->section([0])->{randomizeQuestions} = 1; + srand(42); # Make shuffle predictable $rJSON->createSurveyOrder(); - is($shuffleCalled, 1, 'createSurveyOrder called shuffle on a section'); + @question_order = map {$_->[1]} grep {$_->[0] == 0} @{$rJSON->surveyOrder}; + cmp_deeply(\@question_order, [2,0,1], 'createSurveyOrder shuffled questions in first section'); - $shuffleCalled = 0; $rJSON->survey->section([0])->{randomizeQuestions} = 0; - $rJSON->survey->question([0,0])->{randomizeAnswers} = 1; + $rJSON->survey->question([0,0])->{randomizeAnswers} = 0; $rJSON->createSurveyOrder(); - is($shuffleCalled, 1, 'createSurveyOrder called shuffle on a question'); - - ##Restore the subroutine to the original - *$shuffleName = &$shuffleRef; + my @answer_order = map {@{$_->[2]}} grep {$_->[0] == 3 && $_->[1] == 1} @{$rJSON->surveyOrder}; + cmp_deeply(\@answer_order, [0,1,2,3,4,5,6], 'createSurveyOrder did not shuffle answers'); + + $rJSON->survey->question([3,1])->{randomizeAnswers} = 1; + srand(42); # Make shuffle predictable + $rJSON->createSurveyOrder(); + @answer_order = map {@{$_->[2]}} grep {$_->[0] == 3 && $_->[1] == 1} @{$rJSON->surveyOrder}; + cmp_deeply(\@answer_order, [1,3,4,5,6,0,2], 'createSurveyOrder shuffled answers'); } #################################################### From 90d314d2f1a36271982cd1af301ede5b142099ff Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 01:55:33 +0000 Subject: [PATCH 45/90] Refactored next/last response/section related accessors and mutators (and tests). Added more documentation. --- .../Asset/Wobject/Survey/ResponseJSON.pm | 218 +++++++++++------- t/Asset/Wobject/Survey/ResponseJSON.t | 44 ++-- 2 files changed, 154 insertions(+), 108 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 662b865bc..e63145bcc 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -18,13 +18,38 @@ Package WebGUI::Asset::Wobject::Survey::ResponseJSON =head1 DESCRIPTION -Helper class for WebGUI::Asset::Wobject::Survey. It manages data -from the user, sets the order of questions and answers in the survey, -based on branches, and gotos, and also handles expiring the survey -due to time limits. +Helper class for WebGUI::Asset::Wobject::Survey. "Reponse" in the context of +this Wobject refers to a Survey response (not a single Question response). +ie, this class represents the complete state of a user's response to a Survey instance. + +Instances of this class contain a response property that can be serialized +as JSON to the database to allow for storage and retrieval of the complete state +of a survey response. + +Survey instances that allow users to record multiple responses will persist multiple +instances of this class to the database (one per distinct user response). + +Data stored in this object includes the order in which questions and answers are +presented to the user (surveyOrder), a snapshot of all completed questions +from the user (responses), the most recently answered question (lastResponse), the +number of questions answered (questionsAnswered) and the Survey start time (startTime). This package is not intended to be used by any other Asset in WebGUI. +=head2 surveyOrder + +Many methods in this class operate on the surveyOrder property. + +This data strucutre is a deep set of arrays, similar in structure to +L. + +In general, the surveyOrder data structure looks like: + + [ $sectionIndex, $questionIndex, [ $answerIndex1, $answerIndex2, ....] + +There is one array element for every section and address in the survey. If there are +no questions, or no addresses, those array elements will not be present. + =cut use strict; @@ -41,7 +66,7 @@ Object constructor. =head3 $survey -A WebGUI::Asset::Wobject::Survey::SurveyJSON object that represents the current +A L object that represents the current survey. =head3 $json @@ -64,6 +89,8 @@ sub new { # First define core members.. _survey => $survey, _session => $survey->session, + + # Store all properties that are (de)serialized to/from JSON in a private variable _response => { # Response hash defaults.. @@ -85,10 +112,13 @@ sub new { =head2 createSurveyOrder -Computes the order of Sections, Questions and Aswers for this Survey. The order is represented as -an array of addresses (see L<"Address Parameter">), and is stored in the surveyOrder property. +Computers and stores the order of Sections, Questions and Aswers for this Survey. +The order is represented as an array of addresses +(see L), +and is stored in the surveyOrder property. See also the L<"surveyOrder"> accessor). + Questions and Answers that are set to be randomized are shuffled into a random order. -The survey order leaves a record or what the user was presented with. +The surveyOrder property is useful for keeping a record of what the user was presented with. =cut @@ -136,12 +166,12 @@ sub createSurveyOrder { =head2 session -Accessor method for the local WebGUI::Session reference +Accessor method for the WebGUI::Session reference =cut sub session { - my $self = shift; + my $self = shift; return $self->{_session}; } @@ -149,7 +179,7 @@ sub session { =head2 freeze -Serializes the object to JSON, after deleting the log and survey objects stored in it. +Serializes the internal perl hash representing the Response to a JSON string =cut @@ -160,12 +190,10 @@ sub freeze { #------------------------------------------------------------------- -#Has the survey timed out? - =head2 hasTimedOut ( $limit ) Checks to see whether this survey has timed out, based on the internally stored starting -time, and $limit. +time, and the suppied $limit value. =head3 $limit @@ -174,100 +202,90 @@ How long the user has to take the survey, in minutes. =cut sub hasTimedOut{ - my $self=shift; - my $limit = shift; - return 1 if($self->startTime() + ($limit * 60) < time() and $limit > 0); - return 0; + my $self = shift; + my ($limit) = validate_pos(@_, {type => SCALAR}); + return $limit > 0 && $self->startTime + $limit * 60 < time; } #------------------------------------------------------------------- -#the index of the last surveyOrder entry shown - =head2 lastResponse ([ $responseIndex ]) -Mutator for the index of the last surveyOrder entry shown. With no arguments, -returns the lastResponse index. +Mutator. The lastResponse property represents the index of the most recent surveyOrder entry shown. -=head3 $responseIndex +This method returns (and optionally sets) the value of lastResponse. -If defined, sets the lastResponse to $responseIndex. +=head3 $responseIndex (optional) + +If defined, lastResponse is set to $responseIndex. =cut sub lastResponse { my $self = shift; - my $res = shift; - if ( defined $res ) { - $self->response->{lastResponse} = $res; - } - else { - return $self->response->{lastResponse}; + my ($responseIndex) = validate_pos(@_, {type => SCALAR, optional => 1}); + + if ( defined $responseIndex ) { + $self->response->{lastResponse} = $responseIndex; } + + return $self->response->{lastResponse}; } #------------------------------------------------------------------- =head2 questionsAnswered ([ $questionsAnswered ]) -Mutator for the number of questions answered. With no arguments, -does a set. +Mutator for the number of questions answered. +Returns (and optionally sets) the value of questionsAnswered. -=head3 $questionsAnswered. +=head3 $questionsAnswered (optional) If defined, increments the number of questions by $questionsAnswered =cut sub questionsAnswered { - my $self = shift; - my $answered = shift; - if ( defined $answered ) { - $self->response->{questionsAnswered} += $answered; - } - else { - return $self->response->{questionsAnswered}; + my $self = shift; + my ($questionsAnswered) = validate_pos(@_, {type => SCALAR, optional => 1}); + + if ( defined $questionsAnswered ) { + $self->response->{questionsAnswered} += $questionsAnswered; } + + return $self->response->{questionsAnswered}; } #------------------------------------------------------------------- -=head2 startTime ([ $newStartTime ]) +=head2 startTime ([ $startTime ]) -Mutator for the time the user began the survey. With no arguments, -returns the startTime. +Mutator for the time the user began the survey. +Returns (and optionally sets) the value of startTime. -=head3 $newStarttime +=head3 $startTime (optional) -If defined, sets the starting time to $newStartTime. +If defined, sets the starting time to $startTime. =cut sub startTime { my $self = shift; - my $newTime = shift; - if ( defined $newTime ) { - $self->response->{startTime} = $newTime; - } - else { - return $self->response->{startTime}; + my ($startTime) = validate_pos(@_, {type => SCALAR, optional => 1}); + + if ( defined $startTime ) { + $self->response->{startTime} = $startTime; } + + return $self->response->{startTime}; } #------------------------------------------------------------------- -#array of addresses in which the survey should be presented - =head2 surveyOrder -Accessor for the survey order data structure. It is a deep set of arrays, similar in -structure to a WebGUI::Asset::Wobject::Survey::SurveyJSON address. - - [ $sectionIndex, $questionIndex, [ $answerIndex1, $answerIndex2, ....] - -There is one array element for every section and address in the survey. - -If there are no questions, or no addresses, those array elements will not be present. +Accessor for surveyOrder (see L<"surveyOrder">). +N.B. Use L<"createSurveyOrder"> to modify surveyOrder. =cut @@ -278,47 +296,75 @@ sub surveyOrder { #------------------------------------------------------------------- -=head2 nextSectionId +=head2 nextResponse -Relative to the surveyOrder and the lastResponse index, get the index of the -next section. Note, based on the number of questions in an section, this can -be the same as the current section index. +Returns the index of the next item that should be shown to the user, +that is, the index of the next item in the L<"surveyOrder"> array, +e.g. L<"lastResponse"> + 1. =cut -sub nextSectionId { +sub nextResponse { + my $self = shift; + return $self->lastResponse + 1; +} + +#------------------------------------------------------------------- + +=head2 nextResponseSectionIndex + +Returns the Section index of the next item that should be +shown to the user, that is, the next item in the L<"surveyOrder"> array +relative to L<"lastResponse">. + +We go to the effort of calling this property "nextResponseSectionIndex" +rather than just "nextSectionIndex" to emphasize that this property is +distinct from the "next" section index in the Survey. For example, in +a Section with multiple Questions, the value of nextResponseSectionIndex +will be the same value (the current section index) for all Questions +except the last Question. + +=cut + +sub nextResponseSectionIndex { my $self = shift; return undef if $self->surveyEnd(); - return $self->surveyOrder->[ $self->lastResponse + 1 ]->[0]; + return $self->surveyOrder->[ $self->nextResponse ]->[0]; } #------------------------------------------------------------------- -=head2 nextSection +=head2 nextResponseSection -Relative to the surveyOrder and the lastResponse index, gets the next section. -Note, based on the number of questions in a section, this can be the same as -the current section. +Returns the Section corresponding to the next item that should be +shown to the user, that is, the next item in the L<"surveyOrder"> array +relative to L<"lastResponse">. + +As with L<"nextResponseSectionIndex">, we go to the effort of calling this property "nextResponseSection" +rather than just "nextSection" to emphasize that this property is +distinct from the "next" section in the Survey. =cut -sub nextSection { +sub nextResponseSection { my $self = shift; + return {} if $self->surveyEnd(); - return $self->survey->section( [ $self->surveyOrder->[ $self->lastResponse + 1 ]->[0] ] ); + return $self->survey->section( [ $self->nextResponseSectionIndex ] ); } #------------------------------------------------------------------- -=head2 currentSection +=head2 lastResponseSectionIndex -Relative to the surveyOrder and the lastResponse index, get the current section. +Returns the Section index of the last item that was shown to the user, +based on the L<"surveyOrder"> array and L<"lastResponse">. =cut -sub currentSection { +sub lastResponseSectionIndex { my $self = shift; - return $self->survey->section( [ $self->surveyOrder->[ $self->lastResponse ]->[0] ] ); + return $self->surveyOrder->[ $self->lastResponse ]->[0]; } #------------------------------------------------------------------- @@ -343,7 +389,7 @@ A hash ref of form param data. Each element will look like: "__aid__comment" => "answer comment", } -where __qid__ is a question id, as described in L, and __aid__ is an +where __qid__ is a question id, as described in L<"nextQuestions">, and __aid__ is an answer id, also described there. =head3 terminal processing @@ -404,7 +450,7 @@ sub recordResponses { my $goto; my $gotoExpression; - my $section = $self->nextSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions + my $section = $self->nextResponseSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions if ( $section->{terminal} ) { $sterminal = 1; @@ -413,7 +459,7 @@ sub recordResponses { #There were no questions in the section just displayed, so increment the lastResponse by one if ( ref $questions ne 'ARRAY' ) { - $self->lastResponse( $self->lastResponse + 1 ); + $self->lastResponse( $self->nextResponse ); return [ $sterminal, $terminalUrl ]; } @@ -468,7 +514,7 @@ sub recordResponses { $terminal = 0; } - if($sterminal and $self->nextSection != $self->currentSection){ + if($sterminal and $self->nextResponseSectionIndex != $self->lastResponseSectionIndex){ $terminal = 1; } @@ -691,7 +737,7 @@ sub getPreviousAnswer { Returns an array ref of the next questions in the survey. The number of questions returned is set by the questionsPerPage property of the next section, as determined -by nextSectionId rather than logical section ordering. +by nextResponseSectionIndex rather than logical section ordering. If no questions are available, then it returns an empty array ref. @@ -713,12 +759,12 @@ sub nextQuestions { return [] if $self->surveyEnd; - my $nextSectionId = $self->nextSectionId; + my $nextResponseSectionIndex = $self->nextResponseSectionIndex; - my $qPerPage = $self->survey->section( [ $self->nextSectionId ] )->{questionsPerPage}; + my $qPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage}; #load Previous answer text - my $section = $self->nextSection(); + my $section = $self->nextResponseSection(); $section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg; my $questions; @@ -727,7 +773,7 @@ sub nextQuestions { next if ( !exists $$qAddy[1] ); #skip this if it doesn't have a question (for sections with no questions) - if ( $$qAddy[0] != $nextSectionId ) { + if ( $$qAddy[0] != $nextResponseSectionIndex ) { last; } my %question = %{ $self->survey->question( [ $$qAddy[0], $$qAddy[1] ] ) }; diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 90d600ff4..25b456acf 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -166,51 +166,51 @@ ok( $rJSON->surveyEnd(), 'surveyEnd, with 9 elements, 20 >= end of survey'); #################################################### # -# nextSectionId, nextSection, currentSection +# nextResponseSectionIndex, nextResponseSection, lastResponseSectionIndex # #################################################### $rJSON->lastResponse(0); -is($rJSON->nextSectionId(), 0, 'nextSectionId, lastResponse=0, nextSectionId=0'); +is($rJSON->nextResponseSectionIndex, 0, 'nextResponseSectionIndex, lastResponse=0, nextResponseSectionIndex=0'); cmp_deeply( - $rJSON->nextSection, + $rJSON->nextResponseSection, $rJSON->survey->section([0]), - 'lastResponse=0, nextSection = section 0' + 'lastResponse=0, nextResponseSection = section 0' ); -cmp_deeply( - $rJSON->currentSection, - $rJSON->survey->section([0]), - 'lastResponse=0, currentSection = section 0' +is( + $rJSON->lastResponseSectionIndex, + 0, + 'lastResponse=0, lastResponseSectionIndex = 0' ); $rJSON->lastResponse(2); -is($rJSON->nextSectionId(), 1, 'nextSectionId, lastResponse=2, nextSectionId=1'); +is($rJSON->nextResponseSectionIndex(), 1, 'nextResponseSectionIndex, lastResponse=2, nextResponseSectionIndex=1'); cmp_deeply( - $rJSON->nextSection, + $rJSON->nextResponseSection, $rJSON->survey->section([1]), - 'lastResponse=2, nextSection = section 1' + 'lastResponse=2, nextResponseSection = section 1' ); -cmp_deeply( - $rJSON->currentSection, - $rJSON->survey->section([0]), - 'lastResponse=2, currentSection = section 0' +is( + $rJSON->lastResponseSectionIndex, + 0, + 'lastResponse=2, lastResponseSectionIndex = 0' ); $rJSON->lastResponse(6); -is($rJSON->nextSectionId(), 3, 'nextSectionId, lastResponse=6, nextSectionId=3'); +is($rJSON->nextResponseSectionIndex(), 3, 'nextResponseSectionIndex, lastResponse=6, nextResponseSectionIndex=3'); cmp_deeply( - $rJSON->nextSection, + $rJSON->nextResponseSection, $rJSON->survey->section([3]), - 'lastResponse=0, nextSection = section 3' + 'lastResponse=0, nextResponseSection = section 3' ); cmp_deeply( - $rJSON->currentSection, - $rJSON->survey->section([3]), - 'lastResponse=6, currentSection = section 3' + $rJSON->lastResponseSectionIndex, + 3, + 'lastResponse=6, lastResponseSectionIndex = 3' ); $rJSON->lastResponse(20); -is($rJSON->nextSectionId(), undef, 'nextSectionId, lastResponse > surveyEnd, nextSectionId=undef'); +is($rJSON->nextResponseSectionIndex(), undef, 'nextResponseSectionIndex, lastResponse > surveyEnd, nextResponseSectionIndex=undef'); #################################################### # From 932a033b588037eed465f3e9339636bb9555f88a Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 01:55:56 +0000 Subject: [PATCH 46/90] More ResponseJSON refactoring Removed unnecessary $session argument from recordResponses Refactored nextQuestions to return a list rather than an arrayref Lots more documentation for ResponseJSON More param validation for ResponseJSON Refactored recordResponses Updated tests --- lib/WebGUI/Asset/Wobject/Survey.pm | 8 +- .../Asset/Wobject/Survey/ResponseJSON.pm | 268 ++++++++++-------- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 4 +- t/Asset/Wobject/Survey/ResponseJSON.t | 22 +- 4 files changed, 174 insertions(+), 128 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 87f353814..f06f9dd31 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -802,7 +802,7 @@ sub www_submitQuestions { $self->loadBothJSON(); - my $termInfo = $self->response->recordResponses( $self->session, $responses ); + my $termInfo = $self->response->recordResponses( $responses ); $self->saveResponseJSON(); @@ -871,8 +871,8 @@ sub www_loadQuestions { return $self->surveyEnd(); } - my $questions; - eval { $questions = $self->response->nextQuestions(); }; + my @questions; + eval { @questions = $self->response->nextQuestions(); }; my $section = $self->response->nextSection(); @@ -880,7 +880,7 @@ sub www_loadQuestions { $section->{id} = $self->response->nextSectionId(); $section->{wasRestarted} = $wasRestarted; - my $text = $self->prepareShowSurveyTemplate( $section, $questions ); + my $text = $self->prepareShowSurveyTemplate( $section, \@questions ); return $text; } ## end sub www_loadQuestions diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index e63145bcc..ae17315db 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -22,17 +22,17 @@ Helper class for WebGUI::Asset::Wobject::Survey. "Reponse" in the context of this Wobject refers to a Survey response (not a single Question response). ie, this class represents the complete state of a user's response to a Survey instance. -Instances of this class contain a response property that can be serialized +At the heart of this class is a perl hash that can be serialized as JSON to the database to allow for storage and retrieval of the complete state of a survey response. Survey instances that allow users to record multiple responses will persist multiple instances of this class to the database (one per distinct user response). -Data stored in this object includes the order in which questions and answers are -presented to the user (surveyOrder), a snapshot of all completed questions -from the user (responses), the most recently answered question (lastResponse), the -number of questions answered (questionsAnswered) and the Survey start time (startTime). +Data stored in this object include the order in which questions and answers are +presented to the user (L<"surveyOrder">), a snapshot of all completed questions +from the user (L<"responses">), the most recently answered question (L<"lastResponse">), the +number of questions answered (L<"questionsAnswered">) and the Survey start time (L<"startTime">). This package is not intended to be used by any other Asset in WebGUI. @@ -50,6 +50,26 @@ In general, the surveyOrder data structure looks like: There is one array element for every section and address in the survey. If there are no questions, or no addresses, those array elements will not be present. +=head2 responses + +A response is for a question and is accessed by the exact same address as a survey member. +Questions only contain the comment and an array of answer Responses. +Answers only contain, entered text, entered verbatim, their index in the Survey Question Answer array, +and the assetId to the uploaded file. + +In general, the responses data structure looks like this: + + responses => { + __qid__ => { + comment => "question comment", + }, + __aid__ => { + time => time(), + comment => "answer comment", + value => "answer value", + }, + } + =cut use strict; @@ -72,8 +92,8 @@ survey. =head3 $json A JSON string used to construct a new Perl object. The string should represent -a JSON hash made up of "startTime", "surveyOrder", "responses", "lastReponse" -and "questionsAnswered" keys, with appropriate values. +a JSON hash made up of L<"startTime">, L<"surveyOrder">, L<"responses">, L<"lastReponse"> +and L<"questionsAnswered"> keys, with appropriate values. =cut @@ -115,10 +135,10 @@ sub new { Computers and stores the order of Sections, Questions and Aswers for this Survey. The order is represented as an array of addresses (see L), -and is stored in the surveyOrder property. See also the L<"surveyOrder"> accessor). +and is stored in the L<"surveyOrder"> property. Questions and Answers that are set to be randomized are shuffled into a random order. -The surveyOrder property is useful for keeping a record of what the user was presented with. +The L<"surveyOrder"> property is useful for keeping a record of what the user was presented with. =cut @@ -369,19 +389,15 @@ sub lastResponseSectionIndex { #------------------------------------------------------------------- -=head2 recordResponses ($session, $responses) +=head2 recordResponses ($responses) -Takes survey responses and puts them into the response hash of this object. Does terminal -handling for sections and questions, and goto processing. Advances the survey page if -all required questions have been answered. - -=head3 $session - -A WebGUI session object +Processes and records submitted survey responses in the L<"responses"> data structure. +Does terminal handling, and branch processing, and advances the L<"lastResponse"> index +if all required questions have been answered. =head3 $responses -A hash ref of form param data. Each element will look like: +A hash ref of form param data. Each element should look like: { "__qid__comment" => "question comment", @@ -392,134 +408,175 @@ A hash ref of form param data. Each element will look like: where __qid__ is a question id, as described in L<"nextQuestions">, and __aid__ is an answer id, also described there. -=head3 terminal processing +=head3 Terminal processing Terminal processing for a section and its questions and answers are handled in order. The terminalUrl setting in a question overrides the terminalUrl setting for its section. Similarly, with questions and answers, the last terminalUrl setting of the set of questions is what is returned for the page, with the questions -and answers being answered in surveyOrder. +and answers being answered in L<"surveyOrder">. -=head3 goto processing +=head3 Branch processing -gotos are handled similarly as with terminalUrls. The last goto in the set of questions -wins. - -=head3 responses data structure - -This method also builds an internal data structure with the users' responses. It -is set up like this: - - responses => { - __qid__ => { - comment => "question comment", - }, - __aid__ => { - time => time(), - comment => "answer comment", - value => "answer value", - }, - } +gotos and gotoExpressions are handled similarly as with terminalUrls. The last goto or +gotoExpression in the set of questions wins. =cut sub recordResponses { - my $self = shift; - my $session = shift; - my $responses = shift; - + my $self = shift; + my ($responses) = validate_pos( @_, { type => HASHREF } ); + my %mcTypes = ( - 'Agree/Disagree', 1, 'Certainty', 1, 'Concern', 1, 'Confidence', 1, 'Education', 1, - 'Effectiveness', 1, 'Gender', 1, 'Ideology', 1, 'Importance', 1, 'Likelihood', 1, - 'Party', 1, 'Multiple Choice', 1, 'Oppose/Support', 1, 'Race', 1, 'Risk', 1, - 'Satisfaction', 1, 'Scale', 1, 'Security', 1, 'Threat', 1, 'True/False', 1, - 'Yes/No', 1 + 'Agree/Disagree' => 1, + Certainty => 1, + Concern => 1, + Confidence => 1, + Education => 1, + Effectiveness => 1, + Gender => 1, + Ideology => 1, + Importance => 1, + Likelihood => 1, + Party => 1, + 'Multiple Choice' => 1, + 'Oppose/Support' => 1, + Race => 1, + Risk => 1, + Satisfaction => 1, + Scale => 1, + Security => 1, + Threat => 1, + 'True/False' => 1, + 'Yes/No' => 1, + ); + my %sliderTypes = ( + 'Dual Slider - Range' => 1, + 'Multi Slider - Allocate' => 1, + Slider => 1, + ); + my %textTypes = ( + Currency => 1, + Email => 1, + 'Phone Number' => 1, + Text => 1, + 'Text Date' => 1, + 'TextArea' => 1, + ); + my %fileTypes = ( + 'File Upload' => 1, + ); + my %dateTypes = ( + Date => 1, + 'Date Range' => 1, + ); + my %hiddenTypes = ( + Hidden => 1, ); - my %sliderTypes = ( 'Dual Slider - Range', 1, 'Multi Slider - Allocate', 1, 'Slider', 1 ); - my %textTypes = ( 'Currency', 'Email', 1, 'Phone Number', 1, 'Text', 1, 'Text Date', 1 ,'TextArea', 1); - my %fileTypes = ( 'File Upload', 1 ); - my %dateTypes = ( 'Date', 'Date Range', 1 ); - my %hiddenTypes = ( 'Hidden', 1 ); - #These were just submitted from the user, so we need to see what and how they were (un)answered. - my $questions = $self->nextQuestions(); - my $qAnswered = 1; - my $sterminal = 0; - my $terminal = 0; + # We want to record responses against the "next" response section and questions, since these are + # the items that have just been displayed to the user. + my $section = $self->nextResponseSection(); + my @questions = $self->nextQuestions(); + + # Handle terminal Section.. my $terminalUrl; - my $goto; - my $gotoExpression; - - my $section = $self->nextResponseSection();#which gets the current section for the just submitted questions. IE, current response pointer has not moved forward for these questions - + my $sTerminal = 0; if ( $section->{terminal} ) { - $sterminal = 1; + $sTerminal = 1; $terminalUrl = $section->{terminalUrl}; } - #There were no questions in the section just displayed, so increment the lastResponse by one - if ( ref $questions ne 'ARRAY' ) { + # Handle empty Section.. + if ( !@questions ) { + # No questions to process, so increment lastResponse and return $self->lastResponse( $self->nextResponse ); - return [ $sterminal, $terminalUrl ]; + return [ $sTerminal, $terminalUrl ]; } - for my $question (@$questions) { + # Process Questions in Section.. + my $terminal = 0; + my $allRequiredQsAnswered = 1; + my ($goto, $gotoExpression); + for my $question (@questions) { my $aAnswered = 0; + + # Handle terminal Questions.. if ( $question->{terminal} ) { $terminal = 1; $terminalUrl = $question->{terminalUrl}; } - $self->responses->{ $question->{id} }->{comment} = $responses->{ $question->{id} . "comment" }; + + # Record Question comment + $self->responses->{ $question->{id} }->{comment} = $responses->{ $question->{id} . 'comment' }; + + # Process Answers in Question.. for my $answer ( @{ $question->{answers} } ) { - if ( defined( $responses->{ $answer->{id} } ) - and $responses->{ $answer->{id} } =~ /\S/ ) - { + # Pluck the values out of the responses hash that we want to record.. + my $answerValue = $responses->{ $answer->{id} }; + my $answerComment = $responses->{ $answer->{id} . 'comment' }; + # Proceed if we're satisfied that response is valid.. + if ( defined $answerValue && $answerValue =~ /\S/ ) { $aAnswered = 1; if ( exists $mcTypes{ $question->{questionType} } ) { $self->responses->{ $answer->{id} }->{value} = $answer->{recordedAnswer}; } else { - $self->responses->{ $answer->{id} }->{value} = $responses->{ $answer->{id} }; + $self->responses->{ $answer->{id} }->{value} = $answerValue; } - $self->responses->{ $answer->{id} }->{'time'} = time(); - $self->responses->{ $answer->{id} }->{comment} = $responses->{ $answer->{id} . "comment" }; + $self->responses->{ $answer->{id} }->{time} = time; + $self->responses->{ $answer->{id} }->{comment} = $answerComment; + # Handle terminal Answers.. if ( $answer->{terminal} ) { $terminal = 1; $terminalUrl = $answer->{terminalUrl}; } + # ..and also gotos.. elsif ( $answer->{goto} =~ /\w/ ) { $goto = $answer->{goto}; } + # .. and also gotoExpressions.. elsif ( $answer->{gotoExpression} =~ /\w/ ) { $gotoExpression = $answer->{gotoExpression}; } - } ## end if ( defined( $responses... - } ## end for my $answer ( @{ $question... - $qAnswered = 0 if ( !$aAnswered and $question->{required} ); - if ($aAnswered) { - $self->questionsAnswered( +1 ); + } } - } ## end for my $question (@$questions) - #if all responses completed, move the lastResponse index to the last question shown - if ($qAnswered) { - $self->lastResponse( $self->lastResponse + @$questions ); - $self->goto($goto) if ( defined $goto ); + # Check if a required Question was skipped + if ( $question->{required} && !$aAnswered ) { + $allRequiredQsAnswered = 0; + } + + # If question was answered, increment the questionsAnswered count.. + if ($aAnswered) { + $self->questionsAnswered(+1); + } + } + + # If all required responses were given, proceed onwards! + if ($allRequiredQsAnswered) { + + # Move the lastResponse index to the last question answered + $self->lastResponse( $self->lastResponse + @questions ); + + # Do any requested branching.. + $self->goto($goto) if ( defined $goto ); $self->gotoExpression($gotoExpression) if ( defined $gotoExpression ); } else { + # Required responses were missing, so we don't let the Survey terminate $terminal = 0; } - - if($sterminal and $self->nextResponseSectionIndex != $self->lastResponseSectionIndex){ + + if ( $sTerminal && $self->nextResponseSectionIndex != $self->lastResponseSectionIndex ) { $terminal = 1; - } + } return [ $terminal, $terminalUrl ]; -} ## end sub recordResponses +} #------------------------------------------------------------------- @@ -735,12 +792,10 @@ sub getPreviousAnswer { =head2 nextQuestions -Returns an array ref of the next questions in the survey. The number of questions +Returns an array of the next questions in the survey. The number of questions returned is set by the questionsPerPage property of the next section, as determined by nextResponseSectionIndex rather than logical section ordering. -If no questions are available, then it returns an empty array ref. - Each element of the array ref is a question data structure, from the WebGUI::Asset::Wobject::Survey::SurveyJSON class, with a section sid field (index of the containing section) and question id (section and question id concatenated with a @@ -757,7 +812,7 @@ All questions and answers are safe copies of the survey data. sub nextQuestions { my $self = shift; - return [] if $self->surveyEnd; + return if $self->surveyEnd; my $nextResponseSectionIndex = $self->nextResponseSectionIndex; @@ -767,7 +822,7 @@ sub nextQuestions { my $section = $self->nextResponseSection(); $section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg; - my $questions; + my @questions; for ( my $i = 1; $i <= $qPerPage; $i++ ) { my $qAddy = $self->surveyOrder->[ $self->lastResponse + $i ]; next @@ -787,10 +842,10 @@ sub nextQuestions { $ans{id} = "$$qAddy[0]-$$qAddy[1]-$_"; push( @{ $question{answers} }, \%ans ); } - push( @$questions, \%question ); - } ## end for ( my $i = 1; $i <= ... - return $questions; -} ## end sub nextQuestions + push @questions, \%question; + } + return @questions; +} #------------------------------------------------------------------- @@ -858,11 +913,7 @@ sub returnResponseForReporting { #------------------------------------------------------------------- -#the actual responses to the survey. A response is for a question and is accessed by the exact same address as a survey member. -#Questions only contain the comment and an array of answer Responses. -#Answers only contain, entered text, entered verbatim, their index in the Survey Question Answer array, and the assetId to the uploaded file. - -=head2 session +=head2 response Accessor for the Perl hash containing Response data @@ -875,10 +926,7 @@ sub response { =head2 responses -Returns a reference to the actual responses to the survey. A response is for a question and -is accessed by the exact same address as a survey member. Questions only contain the comment -and an array of answer Responses. Answers only contain, entered text, entered verbatim, -their index in the Survey Question Answer array, and the assetId to the uploaded file. +Mutator for the L<"responses"> property. Note, this is an unsafe reference. @@ -890,14 +938,12 @@ sub responses { if ( defined $responses ) { $self->response->{responses} = $responses; } - else { - return $self->response->{responses}; - } + return $self->response->{responses}; } #------------------------------------------------------------------- -=head2 responses +=head2 survey Returns a referece to the SurveyJSON object that this object was created with. diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 656cc0913..6b0501a00 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -461,8 +461,8 @@ sub getQuestionEditVars { =head2 getValidQuestionTypes A convenience method. Returns a list of question types. If you add a question -type to the Survey, you must handle it here, and also in updateQuestionAnswers() -and administersurvey.js +type to the Survey, you must handle it in the following places: here, updateQuestionAnswers, +recordResponses (ResponseJSON) and administersurvey.js =cut diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 25b456acf..95fc463ea 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -220,14 +220,14 @@ is($rJSON->nextResponseSectionIndex(), undef, 'nextResponseSectionIndex, lastRes $rJSON->lastResponse(20); ok($rJSON->surveyEnd, 'nextQuestions: lastResponse indicates end of survey'); -is_deeply($rJSON->nextQuestions, [], 'nextQuestions returns an empty array ref if there are no questions available'); +is_deeply([$rJSON->nextQuestions], [], 'nextQuestions returns an empty array if there are no questions available'); $rJSON->survey->section([0])->{questionsPerPage} = 2; $rJSON->survey->section([1])->{questionsPerPage} = 2; $rJSON->survey->section([2])->{questionsPerPage} = 2; $rJSON->survey->section([3])->{questionsPerPage} = 2; $rJSON->lastResponse(-1); cmp_deeply( - $rJSON->nextQuestions(), + [$rJSON->nextQuestions], [ superhashof({ sid => 0, @@ -259,7 +259,7 @@ cmp_deeply( $rJSON->lastResponse(1); cmp_deeply( - $rJSON->nextQuestions(), + [$rJSON->nextQuestions], [ superhashof({ sid => 0, @@ -283,9 +283,9 @@ cmp_deeply( $rJSON->lastResponse(4); cmp_deeply( - $rJSON->nextQuestions(), - undef, - 'nextQuestions: returns undef if the next section is empty' + [$rJSON->nextQuestions], + [], + 'nextQuestions: returns an empty array if the next section is empty' ); #################################################### @@ -357,7 +357,7 @@ $rJSON->survey->question([1,0])->{variable} = 's1q0'; $rJSON->survey->answer([1,0,0])->{value} = 3; $rJSON->lastResponse(2); -$rJSON->recordResponses($session, { +$rJSON->recordResponses({ '1-0comment' => 'Section 1, question 0 comment', '1-0-0' => 'First answer', '1-0-0comment' => 'Section 1, question 0, answer 0 comment', @@ -399,7 +399,7 @@ $rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); $rJSON->lastResponse(4); my $terminals; cmp_deeply( - $rJSON->recordResponses($session, {}), + $rJSON->recordResponses({}), [ 0, undef ], 'recordResponses, if section has no questions, returns terminal info in the section. With no terminal info, returns [0, undef]', ); @@ -410,7 +410,7 @@ $rJSON->survey->section([2])->{terminalUrl} = '/terminal'; $rJSON->lastResponse(4); cmp_deeply( - $rJSON->recordResponses($session, {}), + $rJSON->recordResponses({}), [ 1, '/terminal' ], 'recordResponses, if section has no questions, returns terminal info in the section.', ); @@ -421,7 +421,7 @@ $rJSON->survey->question([1,0])->{terminalUrl} = 'question 1-0 terminal'; $rJSON->lastResponse(2); cmp_deeply( - $rJSON->recordResponses($session, { + $rJSON->recordResponses({ '1-0comment' => 'Section 1, question 0 comment', '1-0-0' => 'First answer', '1-0-0comment' => 'Section 1, question 0, answer 0 comment', @@ -457,7 +457,7 @@ $rJSON->lastResponse(2); $rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); cmp_deeply( - $rJSON->recordResponses($session, { + $rJSON->recordResponses({ '1-0comment' => 'Section 1, question 0 comment', '1-0-0' => "\t\t\t\n\n\n\t\t\t", #SOS in whitespace '1-0-0comment' => 'Section 1, question 0, answer 0 comment', From 345989370f52e876e6be502dde6b5ffd541ae27c Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 01:56:18 +0000 Subject: [PATCH 47/90] Renamed ResponseJSON branching methods --- .../Asset/Wobject/Survey/ResponseJSON.pm | 88 ++++++++++++------- t/Asset/Wobject/Survey/ResponseJSON.t | 62 ++++++------- 2 files changed, 87 insertions(+), 63 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index ae17315db..3581c92ca 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -37,11 +37,21 @@ number of questions answered (L<"questionsAnswered">) and the Survey start time This package is not intended to be used by any other Asset in WebGUI. =head2 surveyOrder - -Many methods in this class operate on the surveyOrder property. -This data strucutre is a deep set of arrays, similar in structure to -L. +This data strucutre is an array of Survey addresses (see +L), stored in the order +in which items are presented to the user. + +By making use of L methods which expect address params as +arguments, you can access Section/Question/Answer items in order by iterating over surveyOrder. + +For example: + + # Access sections in order.. + for my $address (@{ $self->surveyOrder }) { + my $section = $self->survey->section( $address ); + # etc.. + } In general, the surveyOrder data structure looks like: @@ -133,12 +143,9 @@ sub new { =head2 createSurveyOrder Computers and stores the order of Sections, Questions and Aswers for this Survey. -The order is represented as an array of addresses -(see L), -and is stored in the L<"surveyOrder"> property. +See L<"surveyOrder">. Questions and Answers that are set to be randomized are shuffled into a random order. -The L<"surveyOrder"> property is useful for keeping a record of what the user was presented with. =cut @@ -563,8 +570,8 @@ sub recordResponses { $self->lastResponse( $self->lastResponse + @questions ); # Do any requested branching.. - $self->goto($goto) if ( defined $goto ); - $self->gotoExpression($gotoExpression) if ( defined $gotoExpression ); + $self->processGoto($goto) if ( defined $goto ); + $self->processGotoExpression($gotoExpression) if ( defined $gotoExpression ); } else { # Required responses were missing, so we don't let the Survey terminate @@ -580,38 +587,55 @@ sub recordResponses { #------------------------------------------------------------------- -=head2 goto ( $variable ) +=head2 processGoto ( $variable ) -Looks through all sections and questions for their variable key, in order. If the requested +Looks through all sections and questions for their variable key, in order. If the requested $variable matches a variable, then the lastResponse is set so that that section or question -is the next displayed. If more than one section or question matches, then the first is used. +is the next displayed. If more than one variable name matches, then the first is used. =head3 $variable -The variable to look for in all sections and questions. +A variable name to match against all section and question variable names. =cut -sub goto { +sub processGoto { my $self = shift; - my $goto = shift; - for ( my $i = 0; $i <= $#{ $self->surveyOrder() }; $i++ ) { - my $section = $self->survey->section( $self->surveyOrder()->[$i] ); - my $question = $self->survey->question( $self->surveyOrder()->[$i] ); - if ( ref $section eq 'HASH' and $section->{variable} eq $goto ) { - $self->lastResponse( $i - 1 ); + my ($goto) = validate_pos(@_, {type => SCALAR}); + + # Iterate over items in order.. + my $itemIndex = 0; + for my $address (@{ $self->surveyOrder }) { + + # Retreive the section and question for this address.. + my $section = $self->survey->section( $address ); + my $question = $self->survey->question( $address ); + + # See if our goto variable matches the section variable.. + if ( ref $section eq 'HASH' && $section->{variable} eq $goto ) { + + # Fudge lastReponse so that the next response item will be our matching item + $self->lastResponse( $itemIndex - 1 ); last; } - if ( ref $question eq 'HASH' and $question->{variable} eq $goto ) { - $self->lastResponse( $i - 1 ); + + # See if our goto variable matches the question variable.. + if ( ref $question eq 'HASH' && $question->{variable} eq $goto ) { + + # Fudge lastReponse so that the next response item will be our matching item + $self->lastResponse( $itemIndex - 1 ); last; } + + # Increment the item index counter + $itemIndex++; } -} ## end sub goto + return; +} #------------------------------------------------------------------- -=head2 gotoExpression ( $gotoExpression ) +=head2 processGotoExpression ( $gotoExpression ) =head3 $gotoExpression @@ -654,7 +678,7 @@ But for now those things can be done manually using the limited subset defined. =cut -sub gotoExpression { +sub processGotoExpression { my $self = shift; my $expression = shift; @@ -670,9 +694,9 @@ sub gotoExpression { } } - # Process gotoExpressions one after the other (first one that's true wins) + # Parse gotoExpressions one after the other (first one that's true wins) foreach my $line (split '\n', $expression) { - my $processed = $self->processGotoExpression($line, \%responses); + my $processed = $self->parseGotoExpression($line, \%responses); next unless $processed; @@ -682,7 +706,7 @@ sub gotoExpression { if ($result) { $self->session->log->debug("Truthy, goto [$processed->{target}]"); - $self->goto($processed->{target}); + $self->processGoto($processed->{target}); return $processed; } else { $self->session->log->debug("Falsy, not branching"); @@ -692,7 +716,7 @@ sub gotoExpression { return; } -=head2 processGotoExpression ( $expression, $responses) +=head2 parseGotoExpression( ( $expression, $responses) Parses a single gotoExpression. Returns undef if processing fails, or the following hashref if things work out well: @@ -722,12 +746,12 @@ the expression generate an error because our list of valid tokens doesn't includ =cut -sub processGotoExpression { +sub parseGotoExpression { my $self = shift; my $expression = shift; my $responses = shift; - $self->session->log->debug("Processing gotoExpression: $expression"); + $self->session->log->debug("Parsing gotoExpression: $expression"); # Valid gotoExpression tokens are.. my $tokens = qr{\s|[-0-9=!<>+*/.()]}; diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 95fc463ea..ba1694682 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -307,13 +307,13 @@ $rJSON->survey->question([3,1])->{variable} = 'goto 3-0'; ##Intentional duplica $rJSON->survey->question([3,2])->{variable} = 'goto 3-2'; $rJSON->lastResponse(0); -$rJSON->goto('goto 80'); +$rJSON->processGoto('goto 80'); is($rJSON->lastResponse(), 0, 'goto: no change in lastResponse if the variable cannot be found'); -$rJSON->goto('goto 1'); +$rJSON->processGoto('goto 1'); is($rJSON->lastResponse(), 2, 'goto: works on existing section'); -$rJSON->goto('goto 0-1'); +$rJSON->processGoto('goto 0-1'); is($rJSON->lastResponse(), 0, 'goto: works on existing question'); -$rJSON->goto('goto 3-0'); +$rJSON->processGoto('goto 3-0'); is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); #################################################### @@ -321,28 +321,28 @@ is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); # processGotoExpression # #################################################### -is($rJSON->processGotoExpression(), +is($rJSON->parseGotoExpression(), undef, 'processGotoExpression undef with empty arguments'); -is($rJSON->processGotoExpression('blah-dee-blah-blah'), +is($rJSON->parseGotoExpression('blah-dee-blah-blah'), undef, '.. and undef with duff expression'); -is($rJSON->processGotoExpression(':'), +is($rJSON->parseGotoExpression(':'), undef, '.. and undef with missing target'); -is($rJSON->processGotoExpression('t1:'), +is($rJSON->parseGotoExpression('t1:'), undef, '.. and undef with missing expression'); -cmp_deeply($rJSON->processGotoExpression('t1: 1'), +cmp_deeply($rJSON->parseGotoExpression('t1: 1'), { target => 't1', expression => '1'}, 'works for simple numeric expression'); -cmp_deeply($rJSON->processGotoExpression('t1: 1 - 23 + 456 * (78 / 9.0)'), +cmp_deeply($rJSON->parseGotoExpression('t1: 1 - 23 + 456 * (78 / 9.0)'), { target => 't1', expression => '1 - 23 + 456 * (78 / 9.0)'}, 'works for expression using all algebraic tokens'); -is($rJSON->processGotoExpression('t1: 1 + &'), undef, '.. but disallows expression containing non-whitelisted token'); -cmp_deeply($rJSON->processGotoExpression('t1: 1 = 3'), +is($rJSON->parseGotoExpression('t1: 1 + &'), undef, '.. but disallows expression containing non-whitelisted token'); +cmp_deeply($rJSON->parseGotoExpression('t1: 1 = 3'), { target => 't1', expression => '1 == 3'}, 'converts single = to =='); -cmp_deeply($rJSON->processGotoExpression('t1: 1 != 3 <= 4 >= 5'), +cmp_deeply($rJSON->parseGotoExpression('t1: 1 != 3 <= 4 >= 5'), { target => 't1', expression => '1 != 3 <= 4 >= 5'}, q{..but doesn't mess with other ops containing =}); -cmp_deeply($rJSON->processGotoExpression('t1: q1 + q2 * q3 - 4', { q1 => 11, q2 => 22, q3 => 33}), +cmp_deeply($rJSON->parseGotoExpression('t1: q1 + q2 * q3 - 4', { q1 => 11, q2 => 22, q3 => 33}), { target => 't1', expression => '11 + 22 * 33 - 4'}, 'substitues q for value'); -cmp_deeply($rJSON->processGotoExpression('t1: a silly var name * 10 + another var name', { 'a silly var name' => 345, 'another var name' => 456}), +cmp_deeply($rJSON->parseGotoExpression('t1: a silly var name * 10 + another var name', { 'a silly var name' => 345, 'another var name' => 456}), { target => 't1', expression => '345 * 10 + 456'}, '..it even works for vars with spaces in their names'); -is($rJSON->processGotoExpression('t1: qX + 3', { q1 => '7'}), +is($rJSON->parseGotoExpression('t1: qX + 3', { q1 => '7'}), undef, q{..but doesn't like invalid var names}); #################################################### @@ -362,30 +362,30 @@ $rJSON->recordResponses({ '1-0-0' => 'First answer', '1-0-0comment' => 'Section 1, question 0, answer 0 comment', }); -is($rJSON->gotoExpression('blah-dee-blah-blah'), undef, 'invalid gotoExpression is false'); -ok($rJSON->gotoExpression('s0: s1q0 = 3'), '3 == 3 is true'); -ok(!$rJSON->gotoExpression('s0: s1q0 = 4'), '3 == 4 is false'); -ok($rJSON->gotoExpression('s0: s1q0 != 2'), '3 != 2 is true'); -ok(!$rJSON->gotoExpression('s0: s1q0 != 3'), '3 != 3 is false'); -ok($rJSON->gotoExpression('s0: s1q0 > 2'), '3 > 2 is true'); -ok($rJSON->gotoExpression('s0: s1q0 < 4'), '3 < 2 is true'); -ok(!$rJSON->gotoExpression('s0: s1q0 >= 4'), '3 >= 4 is false'); -ok(!$rJSON->gotoExpression('s0: s1q0 <= 2'), '3 >= 4 is false'); +is($rJSON->processGotoExpression('blah-dee-blah-blah'), undef, 'invalid gotoExpression is false'); +ok($rJSON->processGotoExpression('s0: s1q0 = 3'), '3 == 3 is true'); +ok(!$rJSON->processGotoExpression('s0: s1q0 = 4'), '3 == 4 is false'); +ok($rJSON->processGotoExpression('s0: s1q0 != 2'), '3 != 2 is true'); +ok(!$rJSON->processGotoExpression('s0: s1q0 != 3'), '3 != 3 is false'); +ok($rJSON->processGotoExpression('s0: s1q0 > 2'), '3 > 2 is true'); +ok($rJSON->processGotoExpression('s0: s1q0 < 4'), '3 < 2 is true'); +ok(!$rJSON->processGotoExpression('s0: s1q0 >= 4'), '3 >= 4 is false'); +ok(!$rJSON->processGotoExpression('s0: s1q0 <= 2'), '3 >= 4 is false'); -cmp_deeply($rJSON->gotoExpression(<<"END_EXPRESSION"), {target => 's2', expression => '3 == 3'}, 'first true expression wins'); +cmp_deeply($rJSON->processGotoExpression(<<"END_EXPRESSION"), {target => 's2', expression => '3 == 3'}, 'first true expression wins'); s0: s1q0 <= 2 s2: s1q0 = 3 END_EXPRESSION -ok(!$rJSON->gotoExpression(<<"END_EXPRESSION"), 'but multiple false expressions still false'); +ok(!$rJSON->processGotoExpression(<<"END_EXPRESSION"), 'but multiple false expressions still false'); s0: s1q0 <= 2 s2: s1q0 = 345 END_EXPRESSION -$rJSON->gotoExpression('s0: s1q0 = 3'); -is($rJSON->lastResponse(), -1, '.. lastResponse changed to -1 due to goto(s0)'); -$rJSON->gotoExpression('s2: s1q0 = 3'); -is($rJSON->lastResponse(), 4, '.. lastResponse changed to 4 due to goto(s2)'); +$rJSON->processGotoExpression('s0: s1q0 = 3'); +is($rJSON->lastResponse(), -1, '.. lastResponse changed to -1 due to processGoto(s0)'); +$rJSON->processGotoExpression('s2: s1q0 = 3'); +is($rJSON->lastResponse(), 4, '.. lastResponse changed to 4 due to processGoto(s2)'); $rJSON->responses({}); $rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); From 3340a675b795df4db415e957e86b920d172cdabf Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 01:56:35 +0000 Subject: [PATCH 48/90] Updated Survey.pm to use new ResponseJSON.pm methods --- lib/WebGUI/Asset/Wobject/Survey.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index f06f9dd31..36cd9ebb6 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -874,10 +874,10 @@ sub www_loadQuestions { my @questions; eval { @questions = $self->response->nextQuestions(); }; - my $section = $self->response->nextSection(); + my $section = $self->response->nextResponseSection(); #return $self->prepareShowSurveyTemplate($section,$questions); - $section->{id} = $self->response->nextSectionId(); + $section->{id} = $self->response->nextResponseSectionIndex(); $section->{wasRestarted} = $wasRestarted; my $text = $self->prepareShowSurveyTemplate( $section, \@questions ); From 306502465c78999febaf0df33eee181c471dc39a Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 01:56:54 +0000 Subject: [PATCH 49/90] Started refactoring nextQuestions in ResponseJSON Made perlcritic happier Extracted getQuestionResponses from processGotoExpression Eliminated getPreviousAnswer --- .../Asset/Wobject/Survey/ResponseJSON.pm | 179 +++++++++++------- t/Asset/Wobject/Survey/ResponseJSON.t | 8 +- 2 files changed, 115 insertions(+), 72 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 3581c92ca..38a3e37f4 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -570,8 +570,8 @@ sub recordResponses { $self->lastResponse( $self->lastResponse + @questions ); # Do any requested branching.. - $self->processGoto($goto) if ( defined $goto ); - $self->processGotoExpression($gotoExpression) if ( defined $gotoExpression ); + $self->processGoto($goto) if ( defined $goto ); ## no critic + $self->processGotoExpression($gotoExpression) if ( defined $gotoExpression ); ## no critic } else { # Required responses were missing, so we don't let the Survey terminate @@ -637,85 +637,122 @@ sub processGoto { =head2 processGotoExpression ( $gotoExpression ) +Processes the given gotoExpression, and triggers a call to L<"processGoto"> if the expression +indicates that we should branch. + =head3 $gotoExpression -The gotoExpression (one expression per line) +The gotoExpression. -=head3 Explanation - -A gotoExpression is a list of expressions (one per line) of the form: +A gotoExpression is a string representing a list of expressions (one per line) of the form: target: expression target: expression + ... This subroutine iterates through the list, processing each line and, all things being well, evaluates the expression. The first expression to evaluate to true triggers a call to goto($target). -The expression is a simple subset of the formula language used in spreadsheet programs such as Excel, OpenOffice, Google Docs etc.. +The expression is a simple subset of the formula language used in spreadsheet programs +such as Excel, OpenOffice, Google Docs etc.. -Here is an example using section variables S1 and S2 as jump targets and question variables Q1-3 in the expression. -It jumps to S1 if the user's answer to Q1 has a value of 3, jumps to S2 if Q2 + Q3 < 10, and otherwise doesn't branch at all (the default). +Here is an example using section variables S1 and S2 as jump targets and question +variables Q1-3 in the expression. It jumps to S1 if the user's answer to Q1 has a value +of 3, jumps to S2 if Q2 + Q3 < 10, and otherwise doesn't branch at all (the default). S1: Q1 = 3 S2: Q2 + Q3 < 10 -=head3 Arguments are evaluated as follows: +Arguments are evaluated as follows: Numeric arguments evaluate as numbers -* No support for strings (and hence no string matching) -* Question variable names (e.g. Q1) evaluate to the numeric value associated with user's answer to that question, or undefined if the user has not answered that question + +=over 4 + +=item * No support for strings (and hence no string matching) + +=item * Question variable names (e.g. Q1) evaluate to the numeric value associated with +user's answer to that question, or undefined if the user has not answered that question + +=back Binary comparisons operators: = != < <= >= > -* return boolean values based on perl's equivalent numeric comparison operators + +=over 4 + +=item * return boolean values based on perl's equivalent numeric comparison operators + +=back Simple math operators: + - * / -* return numeric values -Later we may add Boolean operators: AND( x; y; z; ... ), OR( x; y; z; ... ), NOT( x ) -* args separated by semicolons (presumably because spreadsheet formulas use commas to indicate cell ranges) +=over 4 -Later still you may be able to say AVG(section1) or SUM(section3) and have those functions automatically compute their result over the set of all questions in the given section. +=item * return numeric values + +=back + +Later we may add Boolean operators: AND( x; y; z; ... ), OR( x; y; z; ... ), NOT( x ), with args separated by +semicolons (presumably because spreadsheet formulas use commas to indicate cell ranges) + +Later still you may be able to say AVG(section1) or SUM(section3) and have those functions automatically +compute their result over the set of all questions in the given section. But for now those things can be done manually using the limited subset defined. =cut sub processGotoExpression { my $self = shift; - my $expression = shift; + my ($expression) = validate_pos(@_, {type => SCALAR}); - my %responses = ( - # questionName => response answer value - ); - - # Populate %responses with the user's data.. - foreach my $q (@{ $self->returnResponseForReporting() }) { - if ($q->{questionName} =~ /\w/) { - my $value = $q->{answers}[0]{value}; - $responses{$q->{questionName}} = $value if defined $value; - } - } + my $responses = $self->getQuestionResponses(); # Parse gotoExpressions one after the other (first one that's true wins) - foreach my $line (split '\n', $expression) { - my $processed = $self->parseGotoExpression($line, \%responses); + foreach my $line (split /\n/, $expression) { + my $processed = $self->parseGotoExpression($line, $responses); - next unless $processed; + next if !$processed; # (ab)use perl's eval to evaluate the processed expression - my $result = eval "$processed->{expression}"; - $self->session->log->warn($@) if $@; + my $result = eval "$processed->{expression}"; ## no critic + $self->session->log->warn($@) if $@; ## no critic if ($result) { $self->session->log->debug("Truthy, goto [$processed->{target}]"); $self->processGoto($processed->{target}); return $processed; } else { - $self->session->log->debug("Falsy, not branching"); + $self->session->log->debug('Falsy, not branching'); next; } } return; } +sub getQuestionResponses { + my $self = shift; + + my $responses= { + # questionName => response answer value + }; + + # Populate %responses with the user's data.. + for my $address ( @{ $self->surveyOrder } ) { + my $question = $self->survey->question( $address ); + my $sIndex = $address->[0]; + my $qIndex = $address->[1]; + for my $aIndex (@{ $address->[2] }) { + if ( defined $self->responses->{"$sIndex-$qIndex-$aIndex"} ) { + my $answer = $self->survey->answer( [ $sIndex, $qIndex, $aIndex ] ); + $responses->{$question->{variable}} + = $answer->{value} =~ /\w/ ? $answer->{value} + : $question->{value} + ; + } + } + } + return $responses; +} + =head2 parseGotoExpression( ( $expression, $responses) Parses a single gotoExpression. Returns undef if processing fails, or the following hashref @@ -748,8 +785,7 @@ the expression generate an error because our list of valid tokens doesn't includ sub parseGotoExpression { my $self = shift; - my $expression = shift; - my $responses = shift; + my ($expression, $responses) = validate_pos(@_, { type => SCALAR }, { type => HASHREF, default => {} }); $self->session->log->debug("Parsing gotoExpression: $expression"); @@ -765,13 +801,13 @@ sub parseGotoExpression { return; } - if ( !defined $rest || $rest eq '' ) { + if ( !defined $rest || $rest eq q{} ) { $self->session->log->warn('Expression undefined'); return; } # Replace each questionName with its response value - while ( my ( $questionName, $response ) = each %$responses ) { + while ( my ( $questionName, $response ) = each %{$responses} ) { $rest =~ s/$questionName/$response/g; } @@ -791,26 +827,32 @@ sub parseGotoExpression { }; } -#------------------------------------------------------------------- - -=head2 getPreviousAnswer - -=cut - -sub getPreviousAnswer { - my $self = shift; - my $questionParam = shift; - for my $q ( @{ $self->surveyOrder } ) { - my $question = $self->survey->question( [ $$q[0], $$q[1] ] ); - if ( $question->{variable} eq $questionParam ) { - for ( 0 .. @{ $self->survey->answers( [ $$q[0], $$q[1] ] ) } ) { - if ( exists $self->responses->{ $$q[0] . "-" . $$q[1] . "-" . $_ } ) { - return $self->responses->{ $$q[0] . "-" . $$q[1] . "-" . $_ }->{value}; - } - } - } - } -} +# This method is unnecessary, as it can be expressed as: +# $self->getQuestionResponses()->{$questionParam}; +# +#=head2 getPreviousAnswer +# +#=cut +# +#sub getPreviousAnswer { +# my $self = shift; +# my $questionParam = shift; +# +# for my $address ( @{ $self->surveyOrder } ) { +# my $question = $self->survey->question( $address ); +# if ( $question->{variable} eq $questionParam ) { +# +# # Iterate over answers in the question.. +# for ( 0 .. @{ $self->survey->answers( $address ) } ) { +# use Data::Dumper; +# $self->session->log->warn(Dumper($_)); +# if ( exists $self->responses->{ $address->[0] . "-" . $address->[1] . "-" . $_ } ) { +# return $self->responses->{ $address->[0] . "-" . $address->[1] . "-" . $_ }->{value}; +# } +# } +# } +# } +#} #------------------------------------------------------------------- @@ -838,31 +880,30 @@ sub nextQuestions { return if $self->surveyEnd; - my $nextResponseSectionIndex = $self->nextResponseSectionIndex; - - my $qPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage}; - - #load Previous answer text my $section = $self->nextResponseSection(); - $section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg; + my $sectionIndex = $self->nextResponseSectionIndex; + my $questionsPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage}; + my $questionResponses = $self->getQuestionResponses(); + + $section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; my @questions; - for ( my $i = 1; $i <= $qPerPage; $i++ ) { + for my $i (1 .. $questionsPerPage ) { my $qAddy = $self->surveyOrder->[ $self->lastResponse + $i ]; next if ( !exists $$qAddy[1] ); #skip this if it doesn't have a question (for sections with no questions) - if ( $$qAddy[0] != $nextResponseSectionIndex ) { + if ( $$qAddy[0] != $sectionIndex ) { last; } my %question = %{ $self->survey->question( [ $$qAddy[0], $$qAddy[1] ] ) }; - $question{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg; + $question{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; delete $question{answers}; $question{id} = "$$qAddy[0]-$$qAddy[1]"; $question{sid} = "$$qAddy[0]"; for ( @{ $$qAddy[2] } ) { my %ans = %{ $self->survey->answer( [ $$qAddy[0], $$qAddy[1], $_ ] ) }; - $ans{'text'} =~ s/\[\[([^\%]*?)\]\]/$self->getPreviousAnswer($1)/eg; + $ans{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; $ans{id} = "$$qAddy[0]-$$qAddy[1]-$_"; push( @{ $question{answers} }, \%ans ); } diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index ba1694682..a57aae198 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -9,6 +9,7 @@ use lib "$FindBin::Bin/../../../lib"; use Test::More; use Test::Deep; use Test::MockObject::Extends; +use Test::Exception; use Data::Dumper; use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; @@ -20,7 +21,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 78; +my $tests = 79; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -321,8 +322,9 @@ is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); # processGotoExpression # #################################################### -is($rJSON->parseGotoExpression(), - undef, 'processGotoExpression undef with empty arguments'); +throws_ok { $rJSON->parseGotoExpression() } 'WebGUI::Error::InvalidParam', 'processGotoExpression takes exception to empty arguments'; +is($rJSON->parseGotoExpression(q{}), + undef, '.. and undef with empty expression'); is($rJSON->parseGotoExpression('blah-dee-blah-blah'), undef, '.. and undef with duff expression'); is($rJSON->parseGotoExpression(':'), From fa6976fb408bf61d34bb272907e694815f5e0713 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 05:15:26 +0000 Subject: [PATCH 50/90] More ResponseJSON refactoring. Finished refactoring nextQuestions() Added recordedResponses sub Added getTemplatedText sub Added sectionId, questionId, answerId, sIndex, qIndex, aIndexes abstraction subs --- .../Asset/Wobject/Survey/ResponseJSON.pm | 244 +++++++++++++++--- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 3 + 2 files changed, 211 insertions(+), 36 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 38a3e37f4..8a6d4e221 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -704,7 +704,7 @@ sub processGotoExpression { my $self = shift; my ($expression) = validate_pos(@_, {type => SCALAR}); - my $responses = $self->getQuestionResponses(); + my $responses = $self->recordedResponses(); # Parse gotoExpressions one after the other (first one that's true wins) foreach my $line (split /\n/, $expression) { @@ -728,7 +728,17 @@ sub processGotoExpression { return; } -sub getQuestionResponses { +#------------------------------------------------------------------- + +=head2 recordedResponses + +Returns a hash (reference) of question responses. The hash keys are +question variable names. The hash values are the corresponding answer +values selected by the user. + +=cut + +sub recordedResponses { my $self = shift; my $responses= { @@ -738,10 +748,10 @@ sub getQuestionResponses { # Populate %responses with the user's data.. for my $address ( @{ $self->surveyOrder } ) { my $question = $self->survey->question( $address ); - my $sIndex = $address->[0]; - my $qIndex = $address->[1]; - for my $aIndex (@{ $address->[2] }) { - if ( defined $self->responses->{"$sIndex-$qIndex-$aIndex"} ) { + my ($sIndex, $qIndex) = (sIndex($address), qIndex($address)); + for my $aIndex (aIndexes($address)) { + my $answerId = $self->answerId($sIndex, $qIndex, $aIndex); + if ( defined $self->responses->{$answerId} ) { my $answer = $self->survey->answer( [ $sIndex, $qIndex, $aIndex ] ); $responses->{$question->{variable}} = $answer->{value} =~ /\w/ ? $answer->{value} @@ -753,6 +763,8 @@ sub getQuestionResponses { return $responses; } +#------------------------------------------------------------------- + =head2 parseGotoExpression( ( $expression, $responses) Parses a single gotoExpression. Returns undef if processing fails, or the following hashref @@ -828,7 +840,7 @@ sub parseGotoExpression { } # This method is unnecessary, as it can be expressed as: -# $self->getQuestionResponses()->{$questionParam}; +# $self->recordedResponses()->{$questionParam}; # #=head2 getPreviousAnswer # @@ -854,64 +866,173 @@ sub parseGotoExpression { # } #} + +#------------------------------------------------------------------- + +=head2 getTemplatedText ($text, $responses) + +Scans a string of text for instances of "[[var]]". Looks up each match in the given hash reference +and replaces the string with the associated hash value. + +This method is used to enable simple templating in Survey Section/Question/Answer text. $responses will +usually be a hash of all of the users responses so that their previous responses can be displayed in +the text of later questions. + +=head3 text + +A string of text. e.g. + + Your chose the value [[Q2]] in Question 2 + +=head3 params + +A hash reference. Each matching key in the string will be replaced with its associated value. + +=cut + +sub getTemplatedText { + my $self = shift; + my ($text, $params) = validate_pos(@_, { type => SCALAR }, { type => HASHREF }); + + # Replace all instances of [[var]] with the value from the $params hash reference + $text =~ s/\[\[([^\%]*?)\]\]/$params->{$1}/eg; + + return $text; +} + #------------------------------------------------------------------- =head2 nextQuestions -Returns an array of the next questions in the survey. The number of questions -returned is set by the questionsPerPage property of the next section, as determined -by nextResponseSectionIndex rather than logical section ordering. +Returns a list (array ref) of the Questions that should be shwon on the next page of the Survey. +Each Question also contains a list (array ref) of associated Answers. -Each element of the array ref is a question data structure, from the -WebGUI::Asset::Wobject::Survey::SurveyJSON class, with a section sid field (index of -the containing section) and question id (section and question id concatenated with a -'-') added. The answers array of the question contains answer data structures, also -from WebGUI::Asset::Wobject::Survey::SurveyJSON, with an id field which is the section, -question and answer indexes concatentated together with dashes. +N.B. These are safe copies of the Survey data. -Section and question [[var]] replacements in text fields. +The number of questions is determined by the questionsPerPage property of the 'next' section +in L<"surveyOrder">. -All questions and answers are safe copies of the survey data. +Each element of the array ref returned is a question data structure (see +L), with some additional fields: + +=over 4 + +=item sid Section Id field (see L<"sectionId">) + +=item id Question id (see L<"questionId">. + +=item answers An array of Answers (see L), with +each answer in the array containing an Answer Id (see L<"answerId">) + +=back + +Survey, Question and Answer template text is processed here (see L<"getTemplatedText">) =cut sub nextQuestions { my $self = shift; + # See if we've reached the end of the Survey return if $self->surveyEnd; + # Get some information about the Section that the next response belongs to.. my $section = $self->nextResponseSection(); my $sectionIndex = $self->nextResponseSectionIndex; my $questionsPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage}; - my $questionResponses = $self->getQuestionResponses(); - - $section->{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; + # Get all of the existing question responses (so that we can do Section and Question [[var]] replacements + my $recordedResponses = $self->recordedResponses(); + + # Do text replacement + $section->{text} = $self->getTemplatedText($section->{text}, $recordedResponses); + + # Collect all the questions to be shown on the next page.. my @questions; for my $i (1 .. $questionsPerPage ) { - my $qAddy = $self->surveyOrder->[ $self->lastResponse + $i ]; - next - if ( !exists $$qAddy[1] ); #skip this if it doesn't have a question (for sections with no questions) + my $address = $self->surveyOrder->[ $self->lastResponse + $i ]; + my ($sIndex, $qIndex) = (sIndex($address), qIndex($address)); - if ( $$qAddy[0] != $sectionIndex ) { + # Skip if this is a Section without a Question + if ( !defined $qIndex ) { + next; + } + + # Stop if we have left the Section + if ( $sIndex != $sectionIndex ) { last; } - my %question = %{ $self->survey->question( [ $$qAddy[0], $$qAddy[1] ] ) }; - $question{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; - delete $question{answers}; - $question{id} = "$$qAddy[0]-$$qAddy[1]"; - $question{sid} = "$$qAddy[0]"; - for ( @{ $$qAddy[2] } ) { - my %ans = %{ $self->survey->answer( [ $$qAddy[0], $$qAddy[1], $_ ] ) }; - $ans{'text'} =~ s/\[\[([^\%]*?)\]\]/$questionResponses->{$1}/eg; - $ans{id} = "$$qAddy[0]-$$qAddy[1]-$_"; - push( @{ $question{answers} }, \%ans ); + + # Make a safe copy of the question + my %questionCopy = %{$self->survey->question( $address )}; + + # Do text replacement + $questionCopy{text} = $self->getTemplatedText($questionCopy{text}, $recordedResponses); + + # Add any extra fields we want.. + $questionCopy{id} = $self->questionId($sIndex, $qIndex); + $questionCopy{sid} = $self->sectionId($sIndex); + + # Rebuild the list of anwers with a safe copy + delete $questionCopy{answers}; + for my $aIndex ( aIndexes($address) ) { + my %answerCopy = %{ $self->survey->answer( [ $sIndex, $qIndex, $aIndex ] ) }; + + # Do text replacement + $answerCopy{text} = $self->getTemplatedText($answerCopy{text}, $recordedResponses); + + # Add any extra fields we want.. + $answerCopy{id} = $self->answerId($sIndex, $qIndex, $aIndex); + + push @{ $questionCopy{answers} }, \%answerCopy; } - push @questions, \%question; + push @questions, \%questionCopy; } return @questions; } +=head2 sectionId + +Convenience method to construct a Section Id from the given Section index. + +A Section Id is identical to a Section index. This method is only present for consistency with questionId and answerId. + +=cut + +sub sectionId { + my $self = shift; + my ($sIndex) = validate_pos(@_, { type => SCALAR } ); + return $sIndex; +} + +=head2 questionId + +Convenience method to construct a Question Id from the given Section index and Question index. + +The id is constructed by hyphenating the Section index and Question index. + +=cut + +sub questionId { + my $self = shift; + my ($sIndex, $qIndex) = validate_pos(@_, { type => SCALAR }, { type => SCALAR } ); + return "$sIndex-$qIndex"; +} + +=head2 answerId + +Convenience method to construct an Answer Id from the given Section index, Question index and Answer index. + +The id is constructed by hyphenating all three indices. + +=cut + +sub answerId { + my $self = shift; + my ($sIndex, $qIndex, $aIndex) = validate_pos(@_, { type => SCALAR }, { type => SCALAR }, { type => SCALAR } ); + return "$sIndex-$qIndex-$aIndex"; +} + #------------------------------------------------------------------- =head2 surveyEnd @@ -929,6 +1050,57 @@ sub surveyEnd { #------------------------------------------------------------------- +=head2 sIndex ($address) + +Convenience sub to extract the section index from an address in the L<"surveyOrder"> array. +This method exists purely to improve code readability. +This method is identical to L. + +=cut + +sub sIndex { + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $address->[0]; +} + +#------------------------------------------------------------------- + +=head2 qIndex ($address) + +Convenience sub to extract the question index from an address in the L<"surveyOrder"> array. +This method exists purely to improve code readability. +This method is identical to L. + +=cut + +sub qIndex { + my ($address) = validate_pos(@_, { type => ARRAYREF}); + return $address->[1]; +} + +#------------------------------------------------------------------- + +=head2 aIndexes ($address) + +Convenience sub to extract the array of answer indices from an address in the L<"surveyOrder"> array. +This method exists purely to improve code readability. +Unlike sIndex and qIndex, this method is different to L. +This is because the third element of the L<"surveyOrder"> address array ref in is an array of answer indices. + +=cut + +sub aIndexes { + my ($address) = validate_pos(@_, { type => ARRAYREF}); + + if (my $indexes = $address->[2]) { + return @{ $indexes }; + } + + return; +} + +#------------------------------------------------------------------- + =head2 returnResponsesForReporting =cut diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 6b0501a00..db26db2f8 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -1290,6 +1290,7 @@ sub answer { =head2 sIndex ($address) Convenience sub to extract the section index from a standard $address parameter. See L<"Address Parameter">. +This method exists purely to improve code readability. =cut @@ -1301,6 +1302,7 @@ sub sIndex { =head2 qIndex ($address) Convenience sub to extract the question index from a standard $address parameter. See L<"Address Parameter">. +This method exists purely to improve code readability. =cut @@ -1312,6 +1314,7 @@ sub qIndex { =head2 aIndex ($address) Convenience sub to extract the answer index from a standard $address parameter. See L<"Address Parameter">. +This method exists purely to improve code readability. =cut From 6fddcdaf5fcebe40f8959a4b3905b4216f15f6e9 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 05:51:08 +0000 Subject: [PATCH 51/90] Refactored returnResponseForReporting --- .../Asset/Wobject/Survey/ResponseJSON.pm | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 8a6d4e221..a25b416b3 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -1105,48 +1105,57 @@ sub aIndexes { =cut +# TODO: This sub should make use of recordedResponses + sub returnResponseForReporting { my $self = shift; - my @responses = (); - for my $entry ( @{ $self->surveyOrder } ) { - if ( @$entry == 1 ) { + my @report = (); + for my $address ( @{ $self->surveyOrder } ) { + my ($sIndex, $qIndex) = (sIndex($address), qIndex($address)); + my $section = $self->survey->section( $address ); + my $question = $self->survey->question( [ $sIndex, $qIndex ] ); + my $questionId = $self->questionId($sIndex, $qIndex); + + # Skip if this is a Section without a Question + if ( !defined $qIndex ) { next; } - my @answers; - for ( @{ $$entry[2] } ) { - if ( defined $self->responses->{"$$entry[0]-$$entry[1]-$_"} ) { - $self->responses->{"$$entry[0]-$$entry[1]-$_"}->{id} = $_; - if ( $self->survey->answer( [ $$entry[0], $$entry[1], $_ ] )->{isCorrect} ) { - my $value; - if ( $self->survey->answer( [ $$entry[0], $$entry[1], $_ ] )->{value} =~ /\w/ ) { - $value = $self->survey->answer( [ $$entry[0], $$entry[1], $_ ] )->{value}; - } - else { - $value = $self->survey->question( [ $$entry[0], $$entry[1] ] )->{value}; - } - $self->responses->{"$$entry[0]-$$entry[1]-$_"}->{value} = $value; - $self->responses->{"$$entry[0]-$$entry[1]-$_"}->{isCorrect} = 1; + + my @responses; + for my $aIndex (aIndexes($address)) { + my $answerId = $self->answerId($sIndex, $qIndex, $aIndex); + + if ( $self->responses->{$answerId} ) { + + # Make a safe copy of the response + my %response = %{$self->responses->{$answerId}}; + $response{id} = $aIndex; + + my $answer = $self->survey->answer( [ $sIndex, $qIndex, $aIndex ] ); + if ( $answer->{isCorrect} ) { + $response{value} + = $answer->{value} =~ /\w/ ? $answer->{value} + : $question->{value} + ; + $response{isCorrect} = 1; } else { - $self->responses->{"$$entry[0]-$$entry[1]-$_"}->{isCorrect} = 0; + $response{isCorrect} = 0; } - push( @answers, ( $self->responses->{"$$entry[0]-$$entry[1]-$_"} ) ); - } ## end if ( defined $self->responses... - } ## end for ( @{ $$entry[2] } ) - push( - @responses, ( { - 'section', $$entry[0], - 'question', $$entry[1], - 'sectionName', $self->survey->section( [ $$entry[0] ] )->{variable}, - 'questionName', $self->survey->question( [ $$entry[0], $$entry[1] ] )->{variable}, - 'questionComment', $self->responses->{"$$entry[0]-$$entry[1]"}->{comment}, - 'answers', \@answers - } - ) - ); - } ## end for my $entry ( @{ $self... - return \@responses; -} ## end sub returnResponseForReporting + push @responses, \%response; + } + } + push @report, { + section => $sIndex, + question => $qIndex, + sectionName => $section->{variable}, + questionName => $question->{variable}, + questionComment => $self->responses->{$questionId}->{comment}, + answers => \@responses + }; + } + return \@report; +} #------------------------------------------------------------------- From 675333c54d907692eb11d3c4b471e42eccc19418 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 6 Feb 2009 06:06:29 +0000 Subject: [PATCH 52/90] More documentation tweaks for ResponseJSON --- .../Asset/Wobject/Survey/ResponseJSON.pm | 87 ++++++++----------- 1 file changed, 35 insertions(+), 52 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index a25b416b3..f34726a2c 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -18,9 +18,12 @@ Package WebGUI::Asset::Wobject::Survey::ResponseJSON =head1 DESCRIPTION -Helper class for WebGUI::Asset::Wobject::Survey. "Reponse" in the context of -this Wobject refers to a Survey response (not a single Question response). -ie, this class represents the complete state of a user's response to a Survey instance. +Helper class for WebGUI::Asset::Wobject::Survey. The class deals with both a +"reponse" in the sense of an overall Survey response, and also "response" in +the sense of a single Question response (which is closely related to an Answer but +not quite the same). + +As a whole, this class represents the complete state of a user's response to a Survey instance. At the heart of this class is a perl hash that can be serialized as JSON to the database to allow for storage and retrieval of the complete state @@ -38,7 +41,7 @@ This package is not intended to be used by any other Asset in WebGUI. =head2 surveyOrder -This data strucutre is an array of Survey addresses (see +This data strucutre is an array (reference) of Survey addresses (see L), stored in the order in which items are presented to the user. @@ -62,23 +65,32 @@ no questions, or no addresses, those array elements will not be present. =head2 responses -A response is for a question and is accessed by the exact same address as a survey member. -Questions only contain the comment and an array of answer Responses. -Answers only contain, entered text, entered verbatim, their index in the Survey Question Answer array, -and the assetId to the uploaded file. +This data structure stores a snapshot of all question responses. Both question data and answer data +is stored in this hash reference. -In general, the responses data structure looks like this: +Questions keys are constructed by hypenating the relevant L<"sIndex"> and L<"qIndex">. +Answer keys are constructed by hypenating the relevant L<"sIndex">, L<"qIndex"> and L<"aIndex">. + +Question entries only contain a comment field: + { + ... + questionId => { + comment => "question comment", + } + ... + } + +Answers entries contain: value (the recorded value), time and comment fields. - responses => { - __qid__ => { - comment => "question comment", - }, - __aid__ => { - time => time(), - comment => "answer comment", - value => "answer value", - }, - } + { + ... + answerId => { + value => "answer value", + time => time(), + comment => "answer comment", + }, + ... + } =cut @@ -407,13 +419,12 @@ if all required questions have been answered. A hash ref of form param data. Each element should look like: { - "__qid__comment" => "question comment", - "__aid__" => "answer", - "__aid__comment" => "answer comment", + "questionId-comment" => "question comment", + "answerId" => "answer", + "answerId-comment" => "answer comment", } -where __qid__ is a question id, as described in L<"nextQuestions">, and __aid__ is an -answer id, also described there. +See L<"questionId"> and L<"answerId">. =head3 Terminal processing @@ -839,34 +850,6 @@ sub parseGotoExpression { }; } -# This method is unnecessary, as it can be expressed as: -# $self->recordedResponses()->{$questionParam}; -# -#=head2 getPreviousAnswer -# -#=cut -# -#sub getPreviousAnswer { -# my $self = shift; -# my $questionParam = shift; -# -# for my $address ( @{ $self->surveyOrder } ) { -# my $question = $self->survey->question( $address ); -# if ( $question->{variable} eq $questionParam ) { -# -# # Iterate over answers in the question.. -# for ( 0 .. @{ $self->survey->answers( $address ) } ) { -# use Data::Dumper; -# $self->session->log->warn(Dumper($_)); -# if ( exists $self->responses->{ $address->[0] . "-" . $address->[1] . "-" . $_ } ) { -# return $self->responses->{ $address->[0] . "-" . $address->[1] . "-" . $_ }->{value}; -# } -# } -# } -# } -#} - - #------------------------------------------------------------------- =head2 getTemplatedText ($text, $responses) From 73c02b6803d878a4df40464d4c42687f9e172713 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Tue, 10 Feb 2009 08:00:18 +0000 Subject: [PATCH 53/90] Part-way through merging Survey features from trunk Cleaned up Survey Edit screen, hover help, layout etc.. Added "jump to" AutoComplete box on Section Edit dialog (as per Answer Edit dialog) N.B. "take survey" is currently broken, but will be easy to fix --- lib/WebGUI/Asset/Wobject/Survey.pm | 2 - lib/WebGUI/i18n/English/Asset_Survey.pm | 67 ++++++++---------- root_import_survey.wgpkg | Bin 9586 -> 10490 bytes .../wobject/Survey/editsurvey/object.js | 14 ++-- 4 files changed, 35 insertions(+), 48 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 91dcbf57a..5441660d1 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -20,8 +20,6 @@ use base 'WebGUI::Asset::Wobject'; use WebGUI::Asset::Wobject::Survey::SurveyJSON; use WebGUI::Asset::Wobject::Survey::ResponseJSON; -use Data::Dumper; - #------------------------------------------------------------------- =head2 definition ( session, [definition] ) diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index 41d5a3a90..db4f29ad8 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -83,7 +83,7 @@ our $I18N = { }, 'section name' => { - message => q|Section name:|, + message => q|Section title:|, lastUpdated => 1224686319 }, 'section name description' => { @@ -101,7 +101,7 @@ our $I18N = { lastUpdated => 0 }, 'section custom variable name' => { - message => q|Section custom variable name:|, + message => q|Section variable name:|, lastUpdated => 1224686319 }, 'section custom variable name description' => { @@ -110,7 +110,7 @@ our $I18N = { lastUpdated => 0 }, 'section branch goto variable name' => { - message => q|Section branch goto variable name:|, + message => q|Jump to:|, lastUpdated => 1224686319 }, 'section branch goto variable name description' => { @@ -260,21 +260,21 @@ our $I18N = { context => q|Description of the 'allow comment' field, used as hoverhelp in the edit question dialog.|, lastUpdated => 0 }, - 'cols' => { - message => q|Cols:|, + 'comment cols' => { + message => q|Comment Cols:|, lastUpdated => 1224686319 }, 'cols description' => { - message => q|The number of columns of the textarea input.|, + message => q|The number of columns used for the comment TextArea input field.|, context => q|Description of the 'cols' field, used as hoverhelp in the edit question dialog.|, lastUpdated => 0 }, - 'rows' => { - message => q|Rows:|, + 'comment rows' => { + message => q|Comment Rows:|, lastUpdated => 1224686319 }, 'rows description' => { - message => q|The number of rows of the textarea input.|, + message => q|The number of rows shown for the comment TextArea input field.|, context => q|Description of the 'rows' field, used as hoverhelp in the edit question dialog.|, lastUpdated => 0 }, @@ -329,11 +329,11 @@ our $I18N = { lastUpdated => 0 }, 'recorded answer' => { - message => q|Recorded answer:|, + message => q|Answer title:|, lastUpdated => 1224686319 }, 'recorded answer description' => { - message => q|The answer that will be recorded in the database. The recorded answer will be displayed in a multiple choice question's buttons, only if the question's 'Show text in button' property is set to yes. Otherwise the multiple choice buttons will be empty. |, + message => q|Text to display inside multiple-choice answer buttons (only if 'Show text in button' is enabled for this question).|, context => q|Description of the 'recorded answer' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, @@ -349,9 +349,14 @@ our $I18N = { message => q|The section or question with this variable name will be the next to be displayed after this answer.|, context => q|Description of the 'jump to' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 + }, + 'jump expression description' => { + message => q|An expression used to control complex branching based user responses to previous questions. A branch expression is made up of a list of rules, one per line, along with a branch target for each rule. |, + context => q|Description of the 'jump expression' field, used as hoverhelp in the edit answer dialog.|, + lastUpdated => 0 }, 'text answer' => { - message => q|Text answer|, + message => q|TextArea|, lastUpdated => 1224686319 }, 'is this the correct answer' => { @@ -371,34 +376,30 @@ our $I18N = { message => q|No|, lastUpdated => 1224686319 }, - 'min' => { - message => q|Min|, - lastUpdated => 1224686319 - }, - 'max' => { - message => q|Max|, + 'min label' => { + message => q|Slider Min|, lastUpdated => 1224686319 }, 'min description' => { - message => q|Set the min value of this answer for slider type questions.|, + message => q|The minimum value of this answer for slider type questions.|, context => q|Description of the 'min' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, 'max label' => { - message => q|Max|, + message => q|Slider Max|, lastUpdated => 1224686319 }, 'max description' => { - message => q|Set the max value of this answer for slider type questions.|, + message => q|The maximum value of this answer for slider type questions.|, context => q|Description of the 'max' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, 'step label' => { - message => q|Step|, + message => q|Slider Step|, lastUpdated => 1224686319 }, 'step description' => { - message => q|Set the step value of this answer for slider type questions.|, + message => q|The step value of this answer for slider type questions.|, context => q|Description of the 'step' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, @@ -407,7 +408,7 @@ our $I18N = { lastUpdated => 1224686319 }, 'verbatim description' => { - message => q|Set to yes to add an extra text input to the answer, where the user can enter a single line of text.|, + message => q|Set to yes to add an extra text input to the answer, where the user can enter a single line of text. Typically used to permit a free-text 'other' response.|, context => q|Description of the 'verbatim' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, @@ -416,7 +417,7 @@ our $I18N = { lastUpdated => 1224686319 }, 'answer value description' => { - message => q|Enter a value for this answer.|, + message => q|Assign a numeric scores to this answers. Used in question scoring and jump expressions.|, context => q|Description of the 'answer value' field, used as hoverhelp in the edit answer dialog.|, lastUpdated => 0 }, @@ -1157,18 +1158,6 @@ section/answer.|, lastUpdated => 0, }, - 'min' => { - message => q|The min value of this answer for slider type questions.|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 0, - }, - - 'max' => { - message => q|The max value of this answer for slider type questions..|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 0, - }, - 'step' => { message => q|The step value of this answer for slider type questions..|, context => q|Description of a template variable for a template Help page.|, @@ -1182,13 +1171,13 @@ section/answer.|, }, 'textCols' => { - message => q|The number of columns for textarea answers.|, + message => q|The number of columns for TextArea questions.|, context => q|Description of a template variable for a template Help page.|, lastUpdated => 0, }, 'textRows' => { - message => q|The number of rows for textarea answers.|, + message => q|The number of rows for TextArea questions.|, context => q|Description of a template variable for a template Help page.|, lastUpdated => 0, }, diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index 690eb6c7fc922cd4c032cfba5cc7c1bd8c34d87a..8691beeb78149fe16c6401068f67b19b7025deb4 100644 GIT binary patch literal 10490 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 literal 9586 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 Date: Wed, 11 Feb 2009 09:23:27 +0000 Subject: [PATCH 54/90] Added Params::Validate to testEnvironment.pl --- sbin/testEnvironment.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index eceb5be74..3323b8a8a 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -120,6 +120,7 @@ checkModule("List::MoreUtils", "0.22" ); checkModule("File::Path", "2.04" ); checkModule("Module::Find", "0.06" ); checkModule("Class::C3", "0.19" ); +checkModule("Params::Validate", "0.81" ); failAndExit("Required modules are missing, running no more checks.") if $missingModule; From 2f04648cf748acdcdd5696e55b359483382a264d Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:23:45 +0000 Subject: [PATCH 55/90] Added doAfterTimeLimit column to Survey table --- docs/upgrades/upgrade_7.6.10-7.6.11.pl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/upgrades/upgrade_7.6.10-7.6.11.pl b/docs/upgrades/upgrade_7.6.10-7.6.11.pl index 33190c38c..c3a8cfd8f 100644 --- a/docs/upgrades/upgrade_7.6.10-7.6.11.pl +++ b/docs/upgrades/upgrade_7.6.10-7.6.11.pl @@ -35,6 +35,7 @@ removeBrokenWorkflowInstances($session); undotBinaryExtensions($session); removeProcessRecurringPaymentsFromConfig($session); noSessionSwitch($session); +surveyDoAfterTimeLimit($session); fixDottedAssetIds($session); ##This one should run last finish($session); # this line required @@ -82,6 +83,14 @@ sub undotBinaryExtensions { print "DONE!\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub surveyDoAfterTimeLimit { + my $session = shift; + print "\tAdding column doAfterTimeLimit to Survey table... " unless $quiet; + $session->db->write('alter table Survey add doAfterTimeLimit char(22)'); + print "DONE!\n" unless $quiet; +} + #---------------------------------------------------------------------------- sub fixDottedAssetIds { my $session = shift; From 03be723535ab514253f276cf84cc174130ee13e5 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:24:00 +0000 Subject: [PATCH 56/90] Added note about Params::Validate to gotcha.txt --- docs/gotcha.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 7abfbe510..dc1d19291 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -7,6 +7,10 @@ upgrading from one version to the next, or even between multiple versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.6.11 +-------------------------------------------------------------------- + * WebGUI now requires Params::Validate version 0.81 or greater. + 7.6.10 -------------------------------------------------------------------- * The Survey JSON fields (Survey.surveyJSON and Survey_response.responseJSON) From 3601dfe89edcd68d9c0597ead0cb1ea53a44e918 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:24:19 +0000 Subject: [PATCH 57/90] Re-applied Survey cancel button fix from merge --- www/extras/wobject/Survey/editsurvey/object.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/extras/wobject/Survey/editsurvey/object.js b/www/extras/wobject/Survey/editsurvey/object.js index 9508f07e6..bb049c3a6 100644 --- a/www/extras/wobject/Survey/editsurvey/object.js +++ b/www/extras/wobject/Survey/editsurvey/object.js @@ -49,6 +49,7 @@ Survey.ObjectTemplate = (function(){ text: "Cancel", handler: function(){ this.cancel(); + Survey.Comm.loadSurvey('-'); } }, { text: "Delete", From 25a6fc5e766991d33593a624b8da7feb8ad4cd0f Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:24:39 +0000 Subject: [PATCH 58/90] Added 'max responses per user' i18n and hoverhelp --- lib/WebGUI/Asset/Wobject/Survey.pm | 5 +++-- lib/WebGUI/i18n/English/Asset_Survey.pm | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 5441660d1..3f0afc438 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -107,7 +107,8 @@ sub definition { maxResponsesPerUser => { fieldType => 'integer', defaultValue => 1, - label => 'Max user reponses', + label => $i18n->get('Max user responses'), + hoverHelp => $i18n->get('Max user responses help'), }, overviewTemplateId => { tab => 'display', @@ -185,7 +186,7 @@ sub definition { } ); return $class->SUPER::definition( $session, $definition ); -} ## end sub definition +} #------------------------------------------------------------------- diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index db4f29ad8..50aba4b29 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -530,6 +530,16 @@ the time limit for completing the survey. This message is in the 'take survey' t message => q|When the user finishes the surevey, they will be sent to this URL. Leave blank if no special forwarding is required. The gateway setting from the config file will be automatically added to the URL for you.|, lastUpdated => 1233714385, }, + + 'Max user responses' => { + message => q|Max user responses|, + lastUpdated => 0, + }, + + 'Max user responses help' => { + message => q|The maximum number of times an individual user is allowed to complete the Survey.|, + lastUpdated => 0, + }, 'percentage label' => { message => q|Percentage|, From 99b19aa1803d5f84fb4254da82d9bb2d152ae710 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:24:57 +0000 Subject: [PATCH 59/90] Removed unused responseTemplate from Survey --- docs/upgrades/upgrade_7.6.10-7.6.11.pl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/upgrades/upgrade_7.6.10-7.6.11.pl b/docs/upgrades/upgrade_7.6.10-7.6.11.pl index c3a8cfd8f..dfb0afc00 100644 --- a/docs/upgrades/upgrade_7.6.10-7.6.11.pl +++ b/docs/upgrades/upgrade_7.6.10-7.6.11.pl @@ -36,6 +36,7 @@ undotBinaryExtensions($session); removeProcessRecurringPaymentsFromConfig($session); noSessionSwitch($session); surveyDoAfterTimeLimit($session); +surveyRemoveResponseTemplate($session); fixDottedAssetIds($session); ##This one should run last finish($session); # this line required @@ -89,6 +90,18 @@ sub surveyDoAfterTimeLimit { print "\tAdding column doAfterTimeLimit to Survey table... " unless $quiet; $session->db->write('alter table Survey add doAfterTimeLimit char(22)'); print "DONE!\n" unless $quiet; + print "DONE!\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub surveyRemoveResponseTemplate { + my $session = shift; + print "\tRemoving responseTemplate... " unless $quiet; + $session->db->write('alter table Survey drop responseTemplateId'); + if (my $template = WebGUI::Asset->new($session, 'PBtmpl0000000000000064')) { + $template->purge(); + } + print "DONE!\n" unless $quiet; } #---------------------------------------------------------------------------- From ba6764065b70bb22eda9fb48414c2d2950864f0e Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:25:17 +0000 Subject: [PATCH 60/90] Added Survey Template i18n and hoverhelp --- lib/WebGUI/Asset/Wobject/Survey.pm | 67 +++++++-------- lib/WebGUI/i18n/English/Asset_Survey.pm | 104 ++++++++++++++++++------ 2 files changed, 114 insertions(+), 57 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 3f0afc438..0db07667e 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -110,69 +110,70 @@ sub definition { label => $i18n->get('Max user responses'), hoverHelp => $i18n->get('Max user responses help'), }, - overviewTemplateId => { - tab => 'display', - fieldType => 'template', - defaultValue => 'PBtmpl0000000000000063', - label => 'Overview template id', - namespace => 'Survey/Overview', - }, - gradebookTemplateId => { - tab => 'display', - fieldType => 'template', - label => 'Grabebook template id', - defaultValue => 'PBtmpl0000000000000062', - namespace => 'Survey/Gradebook', - }, - responseTemplateId => { - tab => 'display', - fieldType => 'template', - label => 'Response template id', - defaultValue => 'PBtmpl0000000000000064', - namespace => 'Survey/Response', - }, - surveyEditTemplateId => { - tab => 'display', - fieldType => 'template', - label => 'Survey edit template id', - defaultValue => 'GRUNFctldUgop-qRLuo_DA', - namespace => 'Survey/Edit', - }, surveyTakeTemplateId => { tab => 'display', fieldType => 'template', - label => 'Take survey template id', + label => $i18n->get('Take Survey Template'), + hoverHelp => $i18n->get('Take Survey Template help'), defaultValue => 'd8jMMMRddSQ7twP4l1ZSIw', namespace => 'Survey/Take', }, surveyQuestionsId => { tab => 'display', fieldType => 'template', - label => 'Questions template id', + label => $i18n->get('Questions Template'), + hoverHelp => $i18n->get('Questions Template help'), defaultValue => 'CxMpE_UPauZA3p8jdrOABw', namespace => 'Survey/Take', }, + surveyEditTemplateId => { + tab => 'display', + fieldType => 'template', + label => $i18n->get('Survey Edit Template'), + hoverHelp => $i18n->get('Survey Edit Template help'), + defaultValue => 'GRUNFctldUgop-qRLuo_DA', + namespace => 'Survey/Edit', + }, sectionEditTemplateId => { tab => 'display', fieldType => 'template', - label => 'Section Edit Tempalte', + label => $i18n->get('Section Edit Template'), + hoverHelp => $i18n->get('Section Edit Template help'), defaultValue => '1oBRscNIcFOI-pETrCOspA', namespace => 'Survey/Edit', }, questionEditTemplateId => { tab => 'display', fieldType => 'template', - label => 'Question Edit Tempalte', + label => $i18n->get('Question Edit Template'), + hoverHelp => $i18n->get('Question Edit Template help'), defaultValue => 'wAc4azJViVTpo-2NYOXWvg', namespace => 'Survey/Edit', }, answerEditTemplateId => { tab => 'display', fieldType => 'template', - label => 'Answer Edit Tempalte', + label => $i18n->get('Answer Edit Template'), + hoverHelp => $i18n->get('Answer Edit Template help'), defaultValue => 'AjhlNO3wZvN5k4i4qioWcg', namespace => 'Survey/Edit', }, + overviewTemplateId => { + tab => 'display', + fieldType => 'template', + defaultValue => 'PBtmpl0000000000000063', + label => $i18n->get('Overview Report Template'), + hoverHelp => $i18n->get('Overview Report Template help'), + namespace => 'Survey/Overview', + }, + gradebookTemplateId => { + tab => 'display', + fieldType => 'template', + label => $i18n->get('Grabebook Report Template'), + hoverHelp => $i18n->get('Grabebook Report Template help'), + defaultValue => 'PBtmpl0000000000000062', + namespace => 'Survey/Gradebook', + }, ); push( diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index 50aba4b29..233a9b58a 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -540,6 +540,86 @@ the time limit for completing the survey. This message is in the 'take survey' t message => q|The maximum number of times an individual user is allowed to complete the Survey.|, lastUpdated => 0, }, + + 'Overview Report Template' => { + message => q|Overview Report Template|, + lastUpdated => 0, + }, + + 'Overview Report Template help' => { + message => q|The template used to display the Overview Report.|, + lastUpdated => 0, + }, + + 'Grabebook Report Template' => { + message => q|Grabebook Report Template|, + lastUpdated => 0, + }, + + 'Grabebook Report Template help' => { + message => q|The template used to display the Gradebook Report|, + lastUpdated => 0, + }, + + 'Survey Edit Template' => { + message => q|Survey Edit Template|, + lastUpdated => 0, + }, + + 'Survey Edit Template help' => { + message => q|The template used to display the Survey Edit screen.|, + lastUpdated => 0, + }, + + 'Take Survey Template' => { + message => q|Take Survey Template|, + lastUpdated => 0, + }, + + 'Take Survey Template help' => { + message => q|The template used to control the initial Take Survey screen, from which responses are dynamically loaded into.|, + lastUpdated => 0, + }, + + 'Questions Template' => { + message => q|Questions Template|, + lastUpdated => 0, + }, + + 'Questions Template help' => { + message => q|The template used to display individual questions, which are dynamically loaded into the Take Survey page.|, + lastUpdated => 0, + }, + + 'Section Edit Template' => { + message => q|Section Edit Template|, + lastUpdated => 0, + }, + + 'Section Edit Template help' => { + message => q|The template used to display the Section Edit dialog on the Edit Survey page.|, + lastUpdated => 0, + }, + + 'Question Edit Template' => { + message => q|Question Edit Template|, + lastUpdated => 0, + }, + + 'Question Edit Template help' => { + message => q|The template used to display the Question Edit dialog on the Edit Survey page.|, + lastUpdated => 0, + }, + + 'Answer Edit Template' => { + message => q|Answer Edit Template|, + lastUpdated => 0, + }, + + 'Answer Edit Template help' => { + message => q|The template used to display the Answer Edit dialog on the Edit Survey page.|, + lastUpdated => 0, + }, 'percentage label' => { message => q|Percentage|, @@ -800,12 +880,6 @@ directly inside the answer_loop for other types of questions.|, lastUpdated => 0, }, - 'templateId' => { - message => q|The ID of the template to show the Survey.|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 1168639537, - }, - 'groupToTakeSurvey' => { message => q|The ID of the group that is allowed to take the Survey.|, context => q|Description of a template variable for a template Help page.|, @@ -824,24 +898,6 @@ directly inside the answer_loop for other types of questions.|, lastUpdated => 1168643566, }, - 'overviewTemplateId' => { - message => q|The ID of the template used to show the overview screen.|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 1168643669, - }, - - 'gradebookTemplateId' => { - message => q|The ID of the template used to show the gradebook screen.|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 1168643669, - }, - - 'responseTemplateId' => { - message => q|The ID of the template used to show the Survey Response screen.|, - context => q|Description of a template variable for a template Help page.|, - lastUpdated => 1168643669, - }, - 'survey questions template title' => { message => q|Survey Questions Template|, context => q|The title of a template Help page.|, From d882181fd1b01d1599185f597fef2087334c61b7 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:25:42 +0000 Subject: [PATCH 61/90] Started refactoring the Survey.pm Wobject class Refactored www_jumpTo and added tests Added Params::Validate Improved docs Made call to SurveyJSON->createSurveyOrder() unnecessary Turned ResponseJSON->nextResponse a mutator --- lib/WebGUI/Asset/Wobject/Survey.pm | 177 ++++++++++++------ .../Asset/Wobject/Survey/ResponseJSON.pm | 34 +++- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 2 + t/Asset/Wobject/Survey.t | 57 +++++- t/Asset/Wobject/Survey/ResponseJSON.t | 27 ++- 5 files changed, 211 insertions(+), 86 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 0db07667e..6b8be6e4e 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -19,6 +19,8 @@ use WebGUI::Utility; use base 'WebGUI::Asset::Wobject'; use WebGUI::Asset::Wobject::Survey::SurveyJSON; use WebGUI::Asset::Wobject::Survey::ResponseJSON; +use Params::Validate qw(:all); +Params::Validate::validation_options( on_fail => sub { WebGUI::Error::InvalidParam->throw( error => shift ) } ); #------------------------------------------------------------------- @@ -176,16 +178,15 @@ sub definition { }, ); - push( - @{$definition}, { + push @{$definition}, { assetName => $i18n->get('assetName'), icon => 'survey.gif', autoGenerateForms => 1, tableName => 'Survey', className => 'WebGUI::Asset::Wobject::Survey', properties => \%properties - } - ); + }; + return $class->SUPER::definition( $session, $definition ); } @@ -216,7 +217,8 @@ Override importAssetCollateralData so that surveyJSON gets imported from package sub importAssetCollateralData { my ( $self, $data ) = @_; my $surveyJSON = $data->{properties}{surveyJSON}; - $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", [ $surveyJSON, $self->getId ] ); + $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $surveyJSON, $self->getId ] ); + return; } #------------------------------------------------------------------- @@ -232,7 +234,7 @@ sub duplicate { my $options = shift; my $newAsset = $self->SUPER::duplicate($options); $self->loadSurveyJSON(); - $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", + $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $self->survey->freeze, $newAsset->getId ] ); return $newAsset; } @@ -241,50 +243,63 @@ sub duplicate { =head2 loadSurveyJSON ( ) -Loads the survey collateral into memory so that the survey objects can be created +Loads the survey collateral into memory so that the surveyJSON object can be created. +After this method returns, calls to L<"survey"> will return a surveyJSON instance. +Successive calls to this method have no effect. + +=head3 json (optional) + +A json-encoded string representing a valid SurveyJSON serialization. If provided, +will be used to instantiate the SurveyJSON instance rather than querying the database. =cut sub loadSurveyJSON { my $self = shift; - my $jsonHash = shift; - if ( defined $self->survey ) { return; } #already loaded + my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); - $jsonHash = $self->session->db->quickScalar( "select surveyJSON from Survey where assetId = ?", [ $self->getId ] ) - if ( !defined $jsonHash ); + # Do nothing if survey is already loaded + return if $self->survey; - $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $jsonHash ); + # See if we need to load surveyJSON from the database + if ( ! defined $json ) { + $json + = $self->session->db->quickScalar( 'select surveyJSON from Survey where assetId = ?', [ $self->getId ] ); + } + + # Instantiate the SurveyJSON instance, and store it + return $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); } #------------------------------------------------------------------- =head2 saveSurveyJSON ( ) -Saves the survey collateral to the DB +Serializes the SurveyJSON instance and persists it to the DB =cut - sub saveSurveyJSON { my $self = shift; my $data = $self->survey->freeze(); - $self->session->db->write( "update Survey set surveyJSON = ? where assetId = ?", [ $data, $self->getId ] ); + $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); + + return; } #------------------------------------------------------------------- =head2 survey ( ) -Helper to access the survey object. +Accessor for the SurveyJSON object. See L<"loadSurveyJSON"> and L<"saveSurveyJSON"> =cut -sub survey { return shift->{survey}; } -sub littleBuddy { return shift->{survey}; } -sub allyourbases { return shift->{survey}; } -sub helpmehelpme { return shift->{survey}; } +sub survey { + return shift->{survey}; +} #------------------------------------------------------------------- @@ -298,20 +313,20 @@ sub www_editSurvey { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - my %var; - my $out = $self->processTemplate( \%var, $self->get("surveyEditTemplateId") ); - - return $out; + return $self->processTemplate( {}, $self->get('surveyEditTemplateId') ); } #------------------------------------------------------------------- =head2 www_submitObjectEdit ( ) -This is called when an edit is submitted to a survey object. The POST should contain the id and updated params -of the object, and also if the object is being deleted or copied. +This is called when an edit is submitted to a survey object. The POST should contain the id and updated params +of the object, and also if the object is being deleted or copied. + +In general, the id contains a section index, question index, and answer index, separated by dashes. +See L. =cut @@ -319,14 +334,16 @@ sub www_submitObjectEdit { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - # my $ref = @{from_json($self->session->form->process("data"))}; my $responses = $self->session->form->paramsHashRef(); + # Id is made up of: sectionIndex-questionIndex-answerIndex my @address = split /-/, $responses->{id}; $self->loadSurveyJSON(); + + # See if any special actions were requested.. if ( $responses->{delete} ) { return $self->deleteObject( \@address ); } @@ -334,60 +351,86 @@ sub www_submitObjectEdit { return $self->copyObject( \@address ); } - # each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. + # Each object checks the address and then either updates or passes it to the correct child. New objects will have an index of -1. my $message = $self->survey->update( \@address, $responses ); + # Persist the changes $self->saveSurveyJSON(); + # Return the updated Survey structure return $self->www_loadSurvey( { address => \@address } ); -} ## end sub www_submitObjectEdit +} #------------------------------------------------------------------- -=head2 Allow survey editors to "jump to" a particular section of question in a +=head2 www_jumpTo + +Allow survey editors to jump to a particular section or question in a Survey by tricking Survey into thinking they've completed the survey up to that -point. Useful for survey builders. +point. This is useful for user-testing large Survey instances where you don't want +to waste your time clicking through all of the initial questions to get to the one +you want to look at. + Note that calling this method will delete any existing survey responses for the current user (although only survey builders can call this method so that shouldn't be -a problem +a problem). + =cut sub www_jumpTo { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - my $data = $self->session->form->paramsHashRef(); + my $id = $self->session->form->param('id'); - $self->session->log->debug("jumpTo to $data->{id}"); + # When the Edit Survey screen first loads the first section will have an id of 'undefined' + # In this case, treat it the same as '0' + $id = $id eq 'undefined' ? 0 : $id; + + $self->session->log->debug("www_jumpTo: $id"); # Remove existing responses for current user $self->session->db->write( 'delete from Survey_response where assetId = ? and userId = ?', [ $self->getId, $self->session->user->userId() ] ); - my $responseId = $self->getResponseId(); - $self->loadBothJSON(); + # Create a new response (and trigger loadBothJSON()) + $self->getResponseId(); - # iterate over surveyOrder looking for the jumpTo target - for my $i ( 0 .. $#{ $self->response->surveyOrder() } ) { - my $address = $self->response->surveyOrder()->[$i]; + # Break the $id down into sIndex and qIndex + my ($sIndex, $qIndex) = split /-/, $id; - my @possibilities = ( - $self->survey->section($address), - $self->survey->question($address), - ); - foreach my $possibilty (@possibilities) { - if ( ref $possibilty eq 'HASH' && $possibilty->{id} eq $data->{id} ) { - $self->session->log->debug("Found jumpTo target"); - $self->response->lastResponse( $i - 1 ); - $self->saveResponseJSON(); - last; - } + # Go through items in surveyOrder until we find the item corresponding to $id + my $currentIndex = 0; + for my $address (@{ $self->response->surveyOrder }) { + my ($order_sIndex, $order_qIndex) = @{$address}[0,1]; + + # For starters, check that we're on the right Section + if ($sIndex ne $order_sIndex) { + + # Bad luck, try the next one.. + $currentIndex++; + next; } - } - $self->session->log->debug("Unable to find jumpTo target"); - return $self->www_takeSurvey; + # For a match, either qIndex must be empty (target is a Section), or + # the qIndices must match + if (!defined $qIndex || $qIndex eq $order_qIndex) { + + # Set the nextResponse to be the index we're up to + $self->session->log->debug("Found id: $id at index: $currentIndex in surveyOrder"); + $self->response->nextResponse( $currentIndex ); + $self->saveResponseJSON(); + return $self->www_takeSurvey; + } + + # Keep looking.. + $currentIndex++; + } + + # Search failed, so return the Edit Survey page instead. + $self->session->log->debug("Unable to find id: $id"); + return $self->www_editSurvey; } #------------------------------------------------------------------- @@ -1267,15 +1310,24 @@ If the user is anonymous, the IP is used. Or an email'd or linked code can be u sub getResponseId { my $self = shift; + my %opts = validate(@_, { noCookie => 0 } ); # This is a hack to allow for testing (cookies cause problems) + return $self->{responseId} if ( defined $self->{responseId} ); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); - my $anonId - = $self->session->form->process("userid") - || $self->session->http->getCookies->{"Survey2AnonId"} - || undef; - $self->session->http->setCookie( "Survey2AnonId", $anonId ) if ($anonId); + + my $anonId = $self->session->form->process("userid"); + + unless ($opts{noCookie}) { + $anonId ||= $self->session->http->getCookies->{"Survey2AnonId"}; + } + + $anonId ||= undef; + + unless ($opts{noCookie}) { + $self->session->http->setCookie( "Survey2AnonId", $anonId ) if ($anonId); + } my $responseId; @@ -1336,8 +1388,9 @@ sub getResponseId { anonId => $anonId } ); +# $self->session->log->warn("post: $responseId"); $self->loadBothJSON($responseId); - $self->response->createSurveyOrder(); +# $self->response->createSurveyOrder(); $self->{responseId} = $responseId; $self->saveResponseJSON(); diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index cf7eb2dec..d5a305c61 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -140,7 +140,7 @@ sub new { lastResponse => -1, questionsAnswered => 0, startTime => time(), - surveyOrder => [], + surveyOrder => undef, # And then allow jsonData to override defaults and/or add other members %{$jsonData}, @@ -152,16 +152,17 @@ sub new { #---------------------------------------------------------------------------- -=head2 createSurveyOrder +=head2 initSurveyOrder -Computers and stores the order of Sections, Questions and Aswers for this Survey. -See L<"surveyOrder">. +Computes and stores the order of Sections, Questions and Aswers for this Survey. +See L<"surveyOrder">. You normally don't need to call this, as L<"surveyOrder"> will +call it for you the first time it is used. Questions and Answers that are set to be randomized are shuffled into a random order. =cut -sub createSurveyOrder { +sub initSurveyOrder { my $self = shift; # Order Questions in each Section @@ -324,28 +325,43 @@ sub startTime { =head2 surveyOrder Accessor for surveyOrder (see L<"surveyOrder">). -N.B. Use L<"createSurveyOrder"> to modify surveyOrder. +Initialized on first access via L<"initSurveyOrder">. =cut sub surveyOrder { my $self = shift; + + if (!defined $self->response->{surveyOrder}) { + $self->initSurveyOrder(); + } + return $self->response->{surveyOrder}; } #------------------------------------------------------------------- -=head2 nextResponse +=head2 nextResponse ([ $responseIndex ]) -Returns the index of the next item that should be shown to the user, +Mutator. The index of the next item that should be shown to the user, that is, the index of the next item in the L<"surveyOrder"> array, e.g. L<"lastResponse"> + 1. +=head3 $responseIndex (optional) + +If defined, nextResponse is set to $responseIndex. + =cut sub nextResponse { my $self = shift; - return $self->lastResponse + 1; + my ($responseIndex) = validate_pos(@_, {type => SCALAR, optional => 1}); + + if ( defined $responseIndex ) { + $self->lastResponse($responseIndex - 1); + } + + return $self->lastResponse() + 1 } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 2970d22aa..92aff4a41 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -414,6 +414,7 @@ Adds two variables: =item * id the index of the question's position in its parent's section array joined by dashes '-' +See L. =item * displayed_id @@ -491,6 +492,7 @@ Adds two variables: =item * id The index of the answer's position in its parent's question and section arrays joined by dashes '-' +See L. =item * displayed_id diff --git a/t/Asset/Wobject/Survey.t b/t/Asset/Wobject/Survey.t index a4612c555..1ecc613ff 100644 --- a/t/Asset/Wobject/Survey.t +++ b/t/Asset/Wobject/Survey.t @@ -18,7 +18,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 1; +my $tests = 10; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -37,6 +37,61 @@ $import_node = WebGUI::Asset->getImportNode($session); $survey = $import_node->addChild( { className => 'WebGUI::Asset::Wobject::Survey', } ); isa_ok($survey, 'WebGUI::Asset::Wobject::Survey'); +# Load bare-bones survey, containing a single section (S0) +$survey->loadSurveyJSON(); +$survey->survey->update([0], { variable => 'S0' }); + +# Add 2 questions to S0 +$survey->survey->newObject([0]); # S0Q0 +$survey->survey->update([0,0], { variable => 'S0Q0' }); +$survey->survey->newObject([0]); # S0Q1 +$survey->survey->update([0,1], { variable => 'S0Q1' }); + +# Add a new section (S1) +$survey->survey->newObject([]); # S1 +$survey->survey->update([1], { variable => 'S1' }); + +# Add 2 questions to S1 +$survey->survey->newObject([1]); # S1Q0 +$survey->survey->update([1,0], { variable => 'S1Q0' }); +$survey->survey->newObject([1]); # S1Q1 +$survey->survey->update([1,1], { variable => 'S1Q1' }); + +# Persist to db +$survey->saveSurveyJSON(); + +# Now start a response as admin user +$session->user( { userId =>3 } ); +$survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() + +#for my $address (@{ $survey->response->surveyOrder }) { +# diag (Dumper $address); +#} + +# www_jumpTo +{ + # Check a simple www_jumpTo request + WebGUI::Test->getPage( $survey, 'www_jumpTo', { formParams => {id => '0'} } ); + is( $session->http->getStatus, '201', 'Page request ok' ); # why is "201 - created" status used?? + is($survey->response->nextResponse, 0, 'S0 is the first response'); + + tie my %expectedSurveyOrder, 'Tie::IxHash'; + %expectedSurveyOrder = ( + 'undefined' => 0, + '0' => 0, + '0-0' => 0, + '0-1' => 1, + '1' => 2, + '1-0' => 2, + '1-1' => 3, + ); + while (my ($id, $index) = each %expectedSurveyOrder) { + WebGUI::Test->getPage( $survey, 'www_jumpTo', { formParams => {id => $id} } ); + $survey->loadSurveyJSON(); + is($survey->response->nextResponse, $index, "jumpTo($id) sets nextResponse to $index"); + } +} + } #---------------------------------------------------------------------------- diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index a57aae198..1e25d7a2e 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -21,7 +21,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 79; +my $tests = 78; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -48,7 +48,6 @@ is($responseJSON->lastResponse(), -1, 'new: default lastResponse is -1'); is($responseJSON->questionsAnswered, 0, 'new: questionsAnswered is 0 by default'); cmp_ok((abs$responseJSON->startTime - $newTime), '<=', 2, 'new: by default startTime set to time'); is_deeply( $responseJSON->responses, {}, 'new: by default, responses is an empty hashref'); -is_deeply( $responseJSON->surveyOrder, [], 'new: by default, responses is an empty arrayref'); my $now = time(); my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), qq!{ "startTime": $now }!); @@ -82,13 +81,13 @@ ok( ! $rJSON->hasTimedOut(4*60), 'hasTimedOut, limit check'); #################################################### # -# createSurveyOrder +# initSurveyOrder # #################################################### $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), q!{}!); -$rJSON->createSurveyOrder(); +#$rJSON->initSurveyOrder(); cmp_deeply( $rJSON->surveyOrder, [ @@ -102,7 +101,7 @@ cmp_deeply( [ 3, 1, [0, 1, 2, 3, 4, 5, 6] ], [ 3, 2, [0] ], ], - 'createSurveyOrder, enumerated all sections, questions and answers' + 'initSurveyOrder, enumerated all sections, questions and answers' ); #################################################### @@ -119,7 +118,7 @@ cmp_deeply( #################################################### # -# createSurveyOrder, part 2 +# initSurveyOrder, part 2 # #################################################### @@ -127,27 +126,27 @@ cmp_deeply( my $rJSON = WebGUI::Asset::Wobject::Survey::ResponseJSON->new(buildSurveyJSON($session), q!{}!); $rJSON->survey->section([0])->{randomizeQuestions} = 0; - $rJSON->createSurveyOrder(); + $rJSON->initSurveyOrder(); my @question_order = map {$_->[1]} grep {$_->[0] == 0} @{$rJSON->surveyOrder}; - cmp_deeply(\@question_order, [0,1,2], 'createSurveyOrder did not shuffle questions'); + cmp_deeply(\@question_order, [0,1,2], 'initSurveyOrder did not shuffle questions'); $rJSON->survey->section([0])->{randomizeQuestions} = 1; srand(42); # Make shuffle predictable - $rJSON->createSurveyOrder(); + $rJSON->initSurveyOrder(); @question_order = map {$_->[1]} grep {$_->[0] == 0} @{$rJSON->surveyOrder}; - cmp_deeply(\@question_order, [2,0,1], 'createSurveyOrder shuffled questions in first section'); + cmp_deeply(\@question_order, [2,0,1], 'initSurveyOrder shuffled questions in first section'); $rJSON->survey->section([0])->{randomizeQuestions} = 0; $rJSON->survey->question([0,0])->{randomizeAnswers} = 0; - $rJSON->createSurveyOrder(); + $rJSON->initSurveyOrder(); my @answer_order = map {@{$_->[2]}} grep {$_->[0] == 3 && $_->[1] == 1} @{$rJSON->surveyOrder}; - cmp_deeply(\@answer_order, [0,1,2,3,4,5,6], 'createSurveyOrder did not shuffle answers'); + cmp_deeply(\@answer_order, [0,1,2,3,4,5,6], 'initSurveyOrder did not shuffle answers'); $rJSON->survey->question([3,1])->{randomizeAnswers} = 1; srand(42); # Make shuffle predictable - $rJSON->createSurveyOrder(); + $rJSON->initSurveyOrder(); @answer_order = map {@{$_->[2]}} grep {$_->[0] == 3 && $_->[1] == 1} @{$rJSON->surveyOrder}; - cmp_deeply(\@answer_order, [1,3,4,5,6,0,2], 'createSurveyOrder shuffled answers'); + cmp_deeply(\@answer_order, [1,3,4,5,6,0,2], 'initSurveyOrder shuffled answers'); } #################################################### From 316d133a028824d6f090fecff393507ba416a89b Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:26:05 +0000 Subject: [PATCH 62/90] Improved Survey.pm documentation --- lib/WebGUI/Asset/Wobject/Survey.pm | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 6b8be6e4e..dce476e5a 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -40,7 +40,7 @@ sub definition { my $definition = shift; my $i18n = WebGUI::International->new( $session, 'Asset_Survey' ); my %properties; - tie %properties, 'Tie::IxHash'; + tie %properties, 'Tie::IxHash'; ## no critic %properties = ( templateId => { fieldType => 'template', @@ -196,13 +196,20 @@ sub definition { Override exportAssetData so that surveyJSON is included in package exports etc.. +N.B. Currently ResponseJSON data is not exported. + =cut sub exportAssetData { my $self = shift; + + # Start off with the wobject data that Wobject knows about my $hash = $self->SUPER::exportAssetData(); + + # Add in the SurveyJSON data.. $self->loadSurveyJSON(); $hash->{properties}{surveyJSON} = $self->survey->freeze; + return $hash; } @@ -212,12 +219,17 @@ sub exportAssetData { Override importAssetCollateralData so that surveyJSON gets imported from packages +N.B. Currently ResponseJSON data is not imported. + =cut sub importAssetCollateralData { my ( $self, $data ) = @_; + + # Persist the SurveyJSON data to the database my $surveyJSON = $data->{properties}{surveyJSON}; $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $surveyJSON, $self->getId ] ); + return; } @@ -227,15 +239,22 @@ sub importAssetCollateralData { Override duplicate so that surveyJSON gets duplicated too +N.B. Currently ResponseJSON data is not duplicated. + =cut sub duplicate { my $self = shift; my $options = shift; + + # Start off by letting Wobject duplicate the asset as it knows how my $newAsset = $self->SUPER::duplicate($options); + + # Make sure SurveyJSON data also gets duplicated $self->loadSurveyJSON(); $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $self->survey->freeze, $newAsset->getId ] ); + return $newAsset; } @@ -243,13 +262,17 @@ sub duplicate { =head2 loadSurveyJSON ( ) -Loads the survey collateral into memory so that the surveyJSON object can be created. -After this method returns, calls to L<"survey"> will return a surveyJSON instance. -Successive calls to this method have no effect. +Associates a SurveyJSON object with the Survey instance. A serialized JSON-encoded +version of the SurveyJSON object can be passed in, otherwise the surveyJSON object +will be read from the database. + +Afterwards, calls to L<"survey"> will return a the surveyJSON object. + +Repeated calls to this method have no effect. =head3 json (optional) -A json-encoded string representing a valid SurveyJSON serialization. If provided, +A serialized JSON-encoded string representing a SurveyJSON object. If provided, will be used to instantiate the SurveyJSON instance rather than querying the database. =cut @@ -275,7 +298,7 @@ sub loadSurveyJSON { =head2 saveSurveyJSON ( ) -Serializes the SurveyJSON instance and persists it to the DB +Serializes the SurveyJSON instance and persists it to the database =cut @@ -283,7 +306,6 @@ sub saveSurveyJSON { my $self = shift; my $data = $self->survey->freeze(); - $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); return; @@ -297,8 +319,8 @@ Accessor for the SurveyJSON object. See L<"loadSurveyJSON"> and L<"saveSurveyJSO =cut -sub survey { - return shift->{survey}; +sub survey { + return shift->{survey}; } #------------------------------------------------------------------- @@ -342,7 +364,7 @@ sub www_submitObjectEdit { my @address = split /-/, $responses->{id}; $self->loadSurveyJSON(); - + # See if any special actions were requested.. if ( $responses->{delete} ) { return $self->deleteObject( \@address ); @@ -362,6 +384,7 @@ sub www_submitObjectEdit { } #------------------------------------------------------------------- + =head2 www_jumpTo Allow survey editors to jump to a particular section or question in a From 8d7599d781a2b9c3e89b6d649f2b67c6699436e8 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:26:27 +0000 Subject: [PATCH 63/90] Moved Survey.pm's "survey" and "response" properties to private hash vars and added accessors. So that you don't end up with mind-bending code that looks like: $survey->survey->{survey} --- lib/WebGUI/Asset/Wobject/Survey.pm | 100 ++++++++++++++--------------- t/Asset/Wobject/Survey.t | 28 ++++---- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index dce476e5a..bd30a9fcf 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -208,7 +208,7 @@ sub exportAssetData { # Add in the SurveyJSON data.. $self->loadSurveyJSON(); - $hash->{properties}{surveyJSON} = $self->survey->freeze; + $hash->{properties}{surveyJSON} = $self->surveyJSON->freeze; return $hash; } @@ -253,7 +253,7 @@ sub duplicate { # Make sure SurveyJSON data also gets duplicated $self->loadSurveyJSON(); $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', - [ $self->survey->freeze, $newAsset->getId ] ); + [ $self->surveyJSON->freeze, $newAsset->getId ] ); return $newAsset; } @@ -282,7 +282,7 @@ sub loadSurveyJSON { my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); # Do nothing if survey is already loaded - return if $self->survey; + return if $self->surveyJSON; # See if we need to load surveyJSON from the database if ( ! defined $json ) { @@ -291,7 +291,7 @@ sub loadSurveyJSON { } # Instantiate the SurveyJSON instance, and store it - return $self->{survey} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); + return $self->{_surveyJSON} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); } #------------------------------------------------------------------- @@ -305,7 +305,7 @@ Serializes the SurveyJSON instance and persists it to the database sub saveSurveyJSON { my $self = shift; - my $data = $self->survey->freeze(); + my $data = $self->surveyJSON->freeze(); $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); return; @@ -313,14 +313,14 @@ sub saveSurveyJSON { #------------------------------------------------------------------- -=head2 survey ( ) +=head2 surveyJSON ( ) Accessor for the SurveyJSON object. See L<"loadSurveyJSON"> and L<"saveSurveyJSON"> =cut -sub survey { - return shift->{survey}; +sub surveyJSON { + return shift->{_surveyJSON}; } #------------------------------------------------------------------- @@ -374,7 +374,7 @@ sub www_submitObjectEdit { } # Each object checks the address and then either updates or passes it to the correct child. New objects will have an index of -1. - my $message = $self->survey->update( \@address, $responses ); + my $message = $self->surveyJSON->update( \@address, $responses ); # Persist the changes $self->saveSurveyJSON(); @@ -425,7 +425,7 @@ sub www_jumpTo { # Go through items in surveyOrder until we find the item corresponding to $id my $currentIndex = 0; - for my $address (@{ $self->response->surveyOrder }) { + for my $address (@{ $self->responseJSON->surveyOrder }) { my ($order_sIndex, $order_qIndex) = @{$address}[0,1]; # For starters, check that we're on the right Section @@ -442,7 +442,7 @@ sub www_jumpTo { # Set the nextResponse to be the index we're up to $self->session->log->debug("Found id: $id at index: $currentIndex in surveyOrder"); - $self->response->nextResponse( $currentIndex ); + $self->responseJSON->nextResponse( $currentIndex ); $self->saveResponseJSON(); return $self->www_takeSurvey; } @@ -472,7 +472,7 @@ sub copyObject { $self->loadSurveyJSON(); #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. - $address = $self->survey->copy($address); + $address = $self->surveyJSON->copy($address); $self->saveSurveyJSON(); @@ -503,7 +503,7 @@ sub deleteObject { $self->loadSurveyJSON(); #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. - my $message = $self->survey->remove($address); + my $message = $self->surveyJSON->remove($address); $self->saveSurveyJSON(); @@ -541,7 +541,7 @@ sub www_newObject { $self->loadSurveyJSON(); #Don't save after this as the new object should not stay in the survey - my $address = $self->survey->newObject( \@inAddress ); + my $address = $self->surveyJSON->newObject( \@inAddress ); #The new temp object has an address of NEW, which means it is not a real final address. @@ -569,15 +569,15 @@ sub www_dragDrop { my @bid = split /-/, $p->{before}->{id}; $self->loadSurveyJSON(); - my $target = $self->survey->getObject( \@tid ); - $self->survey->remove( \@tid, 1 ); + my $target = $self->surveyJSON->getObject( \@tid ); + $self->surveyJSON->remove( \@tid, 1 ); my $address = [0]; if ( @tid == 1 ) { #sections can only be inserted after another section so chop off the question and answer portion of $#bid = 0; $bid[0] = -1 if ( !defined $bid[0] ); - $self->survey->insertObject( $target, [ $bid[0] ] ); + $self->surveyJSON->insertObject( $target, [ $bid[0] ] ); } elsif ( @tid == 2 ) { #questions can be moved to any section, but a pushed to the end of a new section. if ( $bid[0] !~ /\d/ ) { @@ -597,23 +597,23 @@ sub www_dragDrop { else { #else move to the end of the selected section - $bid[1] = $#{ $self->survey->questions( [ $bid[0] ] ) }; + $bid[1] = $#{ $self->surveyJSON->questions( [ $bid[0] ] ) }; } } ## end elsif ( @bid == 1 ) - $self->survey->insertObject( $target, [ $bid[0], $bid[1] ] ); + $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1] ] ); } ## end elsif ( @tid == 2 ) elsif ( @tid == 3 ) { #answers can only be rearranged in the same question if ( @bid == 2 and $bid[1] == $tid[1] ) { $bid[2] = -1; - $self->survey->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); + $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } elsif ( @bid == 3 ) { - $self->survey->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); + $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } else { #else put it back where it was - $self->survey->insertObject( $target, \@tid ); + $self->surveyJSON->insertObject( $target, \@tid ); } } @@ -658,7 +658,7 @@ sub www_loadSurvey { my $var = defined $options->{var} ? $options->{var} - : $self->survey->getEditVars($address); + : $self->surveyJSON->getEditVars($address); my $editHtml; if ( $var->{type} eq 'section' ) { @@ -672,7 +672,7 @@ sub www_loadSurvey { } # Generate the list of valid goto targets - my @gotoTargets = $self->survey->getGotoTargets; + my @gotoTargets = $self->surveyJSON->getGotoTargets; my %buttons; $buttons{question} = $$address[0]; @@ -680,7 +680,7 @@ sub www_loadSurvey { $buttons{answer} = "$$address[0]-$$address[1]"; } - my $data = $self->survey->getDragDropList($address); + my $data = $self->surveyJSON->getDragDropList($address); my $html; my ( $scount, $qcount, $acount ) = ( -1, -1, -1 ); my $lastType; @@ -1011,7 +1011,7 @@ sub www_submitQuestions { $self->loadBothJSON(); - my $termInfo = $self->response->recordResponses( $responses ); + my $termInfo = $self->responseJSON->recordResponses( $responses ); $self->saveResponseJSON(); @@ -1076,23 +1076,23 @@ sub www_loadQuestions { $self->session->log->debug('No responseId, surveyEnd'); return $self->surveyEnd(); } - if ( $self->response->hasTimedOut( $self->get('timeLimit') ) ) { + if ( $self->responseJSON->hasTimedOut( $self->get('timeLimit') ) ) { $self->session->log->debug('Response hasTimedOut, surveyEnd'); return $self->surveyEnd( undef, 2 ); } - if ( $self->response->surveyEnd() ) { + if ( $self->responseJSON->surveyEnd() ) { $self->session->log->debug('Response surveyEnd, so calling surveyEnd'); return $self->surveyEnd(); } my @questions; - eval { @questions = $self->response->nextQuestions(); }; + eval { @questions = $self->responseJSON->nextQuestions(); }; - my $section = $self->response->nextResponseSection(); + my $section = $self->responseJSON->nextResponseSection(); #return $self->prepareShowSurveyTemplate($section,$questions); - $section->{id} = $self->response->nextResponseSectionIndex(); + $section->{id} = $self->responseJSON->nextResponseSectionIndex(); $section->{wasRestarted} = $wasRestarted; my $text = $self->prepareShowSurveyTemplate( $section, \@questions ); @@ -1139,8 +1139,8 @@ sub surveyEnd { ); } if ($self->get('doAfterTimeLimit') eq 'restartSurvey' && $completeCode == 2){ - $self->response->startTime(time()); - undef $self->{response}; + $self->responseJSON->startTime(time()); + undef $self->{_responseJSON}; undef $self->{responseId}; return $self->www_loadQuestions('1'); }else{ @@ -1216,12 +1216,12 @@ sub prepareShowSurveyTemplate { } } ## end foreach my $q (@$questions) $section->{'questions'} = $questions; - $section->{'questionsAnswered'} = $self->response->{questionsAnswered}; - $section->{'totalQuestions'} = @{ $self->response->surveyOrder }; + $section->{'questionsAnswered'} = $self->responseJSON->{questionsAnswered}; + $section->{'totalQuestions'} = @{ $self->responseJSON->surveyOrder }; $section->{'showProgress'} = $self->get('showProgress'); $section->{'showTimeLimit'} = $self->get('showTimeLimit'); $section->{'minutesLeft'} - = int( ( ( $self->response->startTime() + ( 60 * $self->get('timeLimit') ) ) - time() ) / 60 ); + = int( ( ( $self->responseJSON->startTime() + ( 60 * $self->get('timeLimit') ) ) - time() ) / 60 ); if(scalar @$questions == ($section->{'totalQuestions'} - $section->{'questionsAnswered'})){ $section->{isLastPage} = 1 @@ -1248,7 +1248,7 @@ The reponse id to load. sub loadBothJSON { my $self = shift; my $rId = shift; - if ( defined $self->survey and defined $self->response ) { return; } + if ( defined $self->surveyJSON and defined $self->responseJSON ) { return; } my $ref = $self->session->db->buildArrayRefOfHashRefs( " select s.surveyJSON,r.responseJSON from Survey s, Survey_response r @@ -1279,7 +1279,7 @@ sub loadResponseJSON { my $jsonHash = shift; my $rId = shift; $rId = defined $rId ? $rId : $self->{responseId}; - if ( defined $self->response and !defined $rId ) { return; } + if ( defined $self->responseJSON and !defined $rId ) { return; } $jsonHash = $self->session->db->quickScalar( @@ -1287,8 +1287,8 @@ sub loadResponseJSON { [ $self->getId, $rId ] ) if ( !defined $jsonHash ); - $self->{response} - = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $self->survey, $jsonHash ); + $self->{_responseJSON} + = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $self->surveyJSON, $jsonHash ); } ## end sub loadResponseJSON #------------------------------------------------------------------- @@ -1302,7 +1302,7 @@ Turns the response object into JSON and saves it to the DB. sub saveResponseJSON { my $self = shift; - my $data = $self->response->freeze(); + my $data = $self->responseJSON->freeze(); $self->session->db->write( "update Survey_response set responseJSON = ? where Survey_responseId = ?", @@ -1311,15 +1311,15 @@ sub saveResponseJSON { #------------------------------------------------------------------- -=head2 response +=head2 responseJSON -Helper to easily grab the response object and prevent typos. +Accessor for the ResponseJSON object =cut -sub response { +sub responseJSON { my $self = shift; - return $self->{response}; + return $self->{_responseJSON}; } #------------------------------------------------------------------- @@ -1413,7 +1413,7 @@ sub getResponseId { ); # $self->session->log->warn("post: $responseId"); $self->loadBothJSON($responseId); -# $self->response->createSurveyOrder(); +# $self->responseJSON->createSurveyOrder(); $self->{responseId} = $responseId; $self->saveResponseJSON(); @@ -1499,7 +1499,7 @@ sub www_viewGradeBook { my $users = $paginator->getPageData; $self->loadSurveyJSON(); - $var->{question_count} = $self->survey->questionCount; + $var->{question_count} = $self->surveyJSON->questionCount; my @responseloop; foreach my $user (@$users) { @@ -1539,7 +1539,7 @@ sub www_viewStatisticalOverview { $self->loadTempReportTable(); $self->loadSurveyJSON(); - my $survey = $self->survey; + my $survey = $self->surveyJSON; my $var = $self->getMenuVars; my $paginator = WebGUI::Paginator->new($self->session,$self->getUrl('func=viewStatisticalOverview')); @@ -1715,7 +1715,7 @@ sub loadTempReportTable { for my $ref (@$refs) { $self->loadResponseJSON( undef, $ref->{Survey_responseId} ); my $count = 1; - for my $q ( @{ $self->response->returnResponseForReporting() } ) { + for my $q ( @{ $self->responseJSON->returnResponseForReporting() } ) { if ( @{ $q->{answers} } == 0 and $q->{comment} =~ /\w/ ) { $self->session->db->write( "insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [ @@ -1737,7 +1737,7 @@ sub loadTempReportTable { ] ); } - } ## end for my $q ( @{ $self->response... + } ## end for my $q ( @{ $self->responseJSON... } ## end for my $ref (@$refs) return 1; } ## end sub loadTempReportTable diff --git a/t/Asset/Wobject/Survey.t b/t/Asset/Wobject/Survey.t index 1ecc613ff..cd76b035b 100644 --- a/t/Asset/Wobject/Survey.t +++ b/t/Asset/Wobject/Survey.t @@ -39,23 +39,23 @@ isa_ok($survey, 'WebGUI::Asset::Wobject::Survey'); # Load bare-bones survey, containing a single section (S0) $survey->loadSurveyJSON(); -$survey->survey->update([0], { variable => 'S0' }); +$survey->surveyJSON->update([0], { variable => 'S0' }); # Add 2 questions to S0 -$survey->survey->newObject([0]); # S0Q0 -$survey->survey->update([0,0], { variable => 'S0Q0' }); -$survey->survey->newObject([0]); # S0Q1 -$survey->survey->update([0,1], { variable => 'S0Q1' }); +$survey->surveyJSON->newObject([0]); # S0Q0 +$survey->surveyJSON->update([0,0], { variable => 'S0Q0' }); +$survey->surveyJSON->newObject([0]); # S0Q1 +$survey->surveyJSON->update([0,1], { variable => 'S0Q1' }); # Add a new section (S1) -$survey->survey->newObject([]); # S1 -$survey->survey->update([1], { variable => 'S1' }); +$survey->surveyJSON->newObject([]); # S1 +$survey->surveyJSON->update([1], { variable => 'S1' }); # Add 2 questions to S1 -$survey->survey->newObject([1]); # S1Q0 -$survey->survey->update([1,0], { variable => 'S1Q0' }); -$survey->survey->newObject([1]); # S1Q1 -$survey->survey->update([1,1], { variable => 'S1Q1' }); +$survey->surveyJSON->newObject([1]); # S1Q0 +$survey->surveyJSON->update([1,0], { variable => 'S1Q0' }); +$survey->surveyJSON->newObject([1]); # S1Q1 +$survey->surveyJSON->update([1,1], { variable => 'S1Q1' }); # Persist to db $survey->saveSurveyJSON(); @@ -64,7 +64,7 @@ $survey->saveSurveyJSON(); $session->user( { userId =>3 } ); $survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() -#for my $address (@{ $survey->response->surveyOrder }) { +#for my $address (@{ $survey->responseJSON->surveyOrder }) { # diag (Dumper $address); #} @@ -73,7 +73,7 @@ $survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() # Check a simple www_jumpTo request WebGUI::Test->getPage( $survey, 'www_jumpTo', { formParams => {id => '0'} } ); is( $session->http->getStatus, '201', 'Page request ok' ); # why is "201 - created" status used?? - is($survey->response->nextResponse, 0, 'S0 is the first response'); + is($survey->responseJSON->nextResponse, 0, 'S0 is the first response'); tie my %expectedSurveyOrder, 'Tie::IxHash'; %expectedSurveyOrder = ( @@ -88,7 +88,7 @@ $survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() while (my ($id, $index) = each %expectedSurveyOrder) { WebGUI::Test->getPage( $survey, 'www_jumpTo', { formParams => {id => $id} } ); $survey->loadSurveyJSON(); - is($survey->response->nextResponse, $index, "jumpTo($id) sets nextResponse to $index"); + is($survey->responseJSON->nextResponse, $index, "jumpTo($id) sets nextResponse to $index"); } } From e139136d087b02d9acc7c04d90259b378d1a9bd7 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:26:50 +0000 Subject: [PATCH 64/90] Made Survey->surveyJSON lazy-load so that you don't have to call loadSurveyJSON() everywhere. --- lib/WebGUI/Asset/Wobject/Survey.pm | 84 ++++++++++-------------------- t/Asset/Wobject/Survey.t | 2 - 2 files changed, 28 insertions(+), 58 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index bd30a9fcf..b927dc407 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -207,7 +207,6 @@ sub exportAssetData { my $hash = $self->SUPER::exportAssetData(); # Add in the SurveyJSON data.. - $self->loadSurveyJSON(); $hash->{properties}{surveyJSON} = $self->surveyJSON->freeze; return $hash; @@ -251,7 +250,6 @@ sub duplicate { my $newAsset = $self->SUPER::duplicate($options); # Make sure SurveyJSON data also gets duplicated - $self->loadSurveyJSON(); $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $self->surveyJSON->freeze, $newAsset->getId ] ); @@ -260,42 +258,6 @@ sub duplicate { #------------------------------------------------------------------- -=head2 loadSurveyJSON ( ) - -Associates a SurveyJSON object with the Survey instance. A serialized JSON-encoded -version of the SurveyJSON object can be passed in, otherwise the surveyJSON object -will be read from the database. - -Afterwards, calls to L<"survey"> will return a the surveyJSON object. - -Repeated calls to this method have no effect. - -=head3 json (optional) - -A serialized JSON-encoded string representing a SurveyJSON object. If provided, -will be used to instantiate the SurveyJSON instance rather than querying the database. - -=cut - -sub loadSurveyJSON { - my $self = shift; - my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); - - # Do nothing if survey is already loaded - return if $self->surveyJSON; - - # See if we need to load surveyJSON from the database - if ( ! defined $json ) { - $json - = $self->session->db->quickScalar( 'select surveyJSON from Survey where assetId = ?', [ $self->getId ] ); - } - - # Instantiate the SurveyJSON instance, and store it - return $self->{_surveyJSON} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); -} - -#------------------------------------------------------------------- - =head2 saveSurveyJSON ( ) Serializes the SurveyJSON instance and persists it to the database @@ -313,14 +275,38 @@ sub saveSurveyJSON { #------------------------------------------------------------------- -=head2 surveyJSON ( ) +=head2 surveyJSON ( [json] ) -Accessor for the SurveyJSON object. See L<"loadSurveyJSON"> and L<"saveSurveyJSON"> +Lazy-loading mutator for the L object. + +The surveyJSON property associates a SurveyJSON object with this Survey instance. +It is stored in the database as a serialized JSON-encoded string in the surveyJSON field. + +See also L<"saveSurveyJSON">. + +=head3 json (optional) + +A serialized JSON-encoded string representing a SurveyJSON object. If provided, +will be used to instantiate the SurveyJSON instance rather than querying the database. =cut sub surveyJSON { - return shift->{_surveyJSON}; + my $self = shift; + my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); + + if (!$self->{_surveyJSON} || $json) { + + # See if we need to load surveyJSON from the database + if ( ! defined $json ) { + $json = $self->session->db->quickScalar( 'select surveyJSON from Survey where assetId = ?', [ $self->getId ] ); + } + + # Instantiate the SurveyJSON instance, and store it + $self->{_surveyJSON} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); + } + + return $self->{_surveyJSON}; } #------------------------------------------------------------------- @@ -363,8 +349,6 @@ sub www_submitObjectEdit { # Id is made up of: sectionIndex-questionIndex-answerIndex my @address = split /-/, $responses->{id}; - $self->loadSurveyJSON(); - # See if any special actions were requested.. if ( $responses->{delete} ) { return $self->deleteObject( \@address ); @@ -469,8 +453,6 @@ Returns the address to the new object. sub copyObject { my ( $self, $address ) = @_; - $self->loadSurveyJSON(); - #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. $address = $self->surveyJSON->copy($address); @@ -500,8 +482,6 @@ that section. The third element is the index of the answer. sub deleteObject { my ( $self, $address ) = @_; - $self->loadSurveyJSON(); - #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. my $message = $self->surveyJSON->remove($address); @@ -538,8 +518,6 @@ sub www_newObject { my @inAddress = split /-/, $ids; - $self->loadSurveyJSON(); - #Don't save after this as the new object should not stay in the survey my $address = $self->surveyJSON->newObject( \@inAddress ); @@ -568,7 +546,6 @@ sub www_dragDrop { my @tid = split /-/, $p->{target}->{id}; my @bid = split /-/, $p->{before}->{id}; - $self->loadSurveyJSON(); my $target = $self->surveyJSON->getObject( \@tid ); $self->surveyJSON->remove( \@tid, 1 ); my $address = [0]; @@ -639,8 +616,6 @@ sub www_loadSurvey { my ( $self, $options ) = @_; my $editflag = 1; - $self->loadSurveyJSON(); - my $address = defined $options->{address} ? $options->{address} : undef; if ( !defined $address ) { if ( my $inAddress = $self->session->form->process("data") ) { @@ -1254,7 +1229,7 @@ sub loadBothJSON { from Survey s, Survey_response r where s.assetId = ? and r.Survey_responseId = ?", [ $self->getId, $rId ] ); - $self->loadSurveyJSON( $ref->[0]->{surveyJSON} ); + $self->surveyJSON( $ref->[0]->{surveyJSON} ); $self->loadResponseJSON( $ref->[0]->{responseJSON}, $rId ); } @@ -1498,7 +1473,6 @@ sub www_viewGradeBook { where assetId=".$db->quote($self->getId)." order by username,ipAddress,startDate"); my $users = $paginator->getPageData; - $self->loadSurveyJSON(); $var->{question_count} = $self->surveyJSON->questionCount; my @responseloop; @@ -1538,7 +1512,6 @@ sub www_viewStatisticalOverview { unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); $self->loadTempReportTable(); - $self->loadSurveyJSON(); my $survey = $self->surveyJSON; my $var = $self->getMenuVars; @@ -1708,7 +1681,6 @@ Loads the responses from the survey into the Survey_tempReport table, so that ot sub loadTempReportTable { my $self = shift; - $self->loadSurveyJSON(); my $refs = $self->session->db->buildArrayRefOfHashRefs( "select * from Survey_response where assetId = ?", [ $self->getId() ] ); $self->session->db->write( "delete from Survey_tempReport where assetId = ?", [ $self->getId() ] ); diff --git a/t/Asset/Wobject/Survey.t b/t/Asset/Wobject/Survey.t index cd76b035b..ba75ee52c 100644 --- a/t/Asset/Wobject/Survey.t +++ b/t/Asset/Wobject/Survey.t @@ -38,7 +38,6 @@ $survey = $import_node->addChild( { className => 'WebGUI::Asset::Wobject::Survey isa_ok($survey, 'WebGUI::Asset::Wobject::Survey'); # Load bare-bones survey, containing a single section (S0) -$survey->loadSurveyJSON(); $survey->surveyJSON->update([0], { variable => 'S0' }); # Add 2 questions to S0 @@ -87,7 +86,6 @@ $survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() ); while (my ($id, $index) = each %expectedSurveyOrder) { WebGUI::Test->getPage( $survey, 'www_jumpTo', { formParams => {id => $id} } ); - $survey->loadSurveyJSON(); is($survey->responseJSON->nextResponse, $index, "jumpTo($id) sets nextResponse to $index"); } } From 16c87b9cbaa09f117606dab844689b5769ee5a45 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:27:10 +0000 Subject: [PATCH 65/90] Made Survey's responseJSON property lazy-load so that you don't have to call loadResponseJSON and/or loadBothJSON everywhere --- lib/WebGUI/Asset/Wobject/Survey.pm | 139 +++++++++++++---------------- 1 file changed, 63 insertions(+), 76 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index b927dc407..896d912cb 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -277,10 +277,9 @@ sub saveSurveyJSON { =head2 surveyJSON ( [json] ) -Lazy-loading mutator for the L object. +Lazy-loading mutator for the L property. -The surveyJSON property associates a SurveyJSON object with this Survey instance. -It is stored in the database as a serialized JSON-encoded string in the surveyJSON field. +It is stored in the database as a serialized JSON-encoded string in the surveyJSON db field. See also L<"saveSurveyJSON">. @@ -296,12 +295,12 @@ sub surveyJSON { my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); if (!$self->{_surveyJSON} || $json) { - + # See if we need to load surveyJSON from the database - if ( ! defined $json ) { + if ( !defined $json ) { $json = $self->session->db->quickScalar( 'select surveyJSON from Survey where assetId = ?', [ $self->getId ] ); } - + # Instantiate the SurveyJSON instance, and store it $self->{_surveyJSON} = WebGUI::Asset::Wobject::Survey::SurveyJSON->new( $self->session, $json ); } @@ -311,6 +310,49 @@ sub surveyJSON { #------------------------------------------------------------------- +=head2 responseJSON ( [json], [responseId] ) + +Lazy-loading mutator for the L property. + +It is stored in the database as a serialized JSON-encoded string in the responseJSON db field. + +See also L<"saveResponseJSON">. + +=head3 json (optional) + +A serialized JSON-encoded string representing a ResponseJSON object. If provided, +will be used to instantiate the ResponseJSON instance rather than querying the database. + +=head3 responseId (optional) + +A responseId to use when retrieving ResponseJSON from the database (defaults to the value returned by L<"responseId">) + +=cut + +sub responseJSON { + my $self = shift; + my ($json, $responseId) = validate_pos(@_, { type => SCALAR | UNDEF, optional => 1 }, { type => SCALAR, optional => 1}); + + if (!defined $responseId) { + $responseId = $self->{responseId}; + } + + if (!$self->{_responseJSON} || $json) { + + # See if we need to load responseJSON from the database + if (!defined $json) { + $json = $self->session->db->quickScalar( 'select responseJSON from Survey_response where assetId = ? and Survey_responseId = ?', [ $self->getId, $responseId ] ); + } + + # Instantiate the ResponseJSON instance, and store it + $self->{_responseJSON} = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $self->surveyJSON, $json ); + } + + return $self->{_responseJSON}; +} + +#------------------------------------------------------------------- + =head2 www_editSurvey ( ) Loads the initial edit survey page. All other edit actions are JSON calls from this page. @@ -984,7 +1026,7 @@ sub www_submitQuestions { my @goodResponses = keys %$responses; #load everything. - $self->loadBothJSON(); +# $self->loadBothJSON(); my $termInfo = $self->responseJSON->recordResponses( $responses ); @@ -1223,51 +1265,18 @@ The reponse id to load. sub loadBothJSON { my $self = shift; my $rId = shift; - if ( defined $self->surveyJSON and defined $self->responseJSON ) { return; } +# if ( defined $self->surveyJSON and defined $self->responseJSON ) { return; } my $ref = $self->session->db->buildArrayRefOfHashRefs( " select s.surveyJSON,r.responseJSON from Survey s, Survey_response r where s.assetId = ? and r.Survey_responseId = ?", [ $self->getId, $rId ] ); $self->surveyJSON( $ref->[0]->{surveyJSON} ); - $self->loadResponseJSON( $ref->[0]->{responseJSON}, $rId ); + $self->responseJSON( $ref->[0]->{responseJSON}, $rId ); } #------------------------------------------------------------------- -=head2 loadResponseJSON([$jsonHash],[$rId]) - -Loads the response object from JSON. - -=head3 $jsonHash - -Optional, but if the hash has been pulled from the DB before, there is no need to pull it again. - -=head3 $rId - -Optional, but if not passed in, it is grabbed. - -=cut - -sub loadResponseJSON { - my $self = shift; - my $jsonHash = shift; - my $rId = shift; - $rId = defined $rId ? $rId : $self->{responseId}; - if ( defined $self->responseJSON and !defined $rId ) { return; } - - $jsonHash - = $self->session->db->quickScalar( - "select responseJSON from Survey_response where assetId = ? and Survey_responseId = ?", - [ $self->getId, $rId ] ) - if ( !defined $jsonHash ); - - $self->{_responseJSON} - = WebGUI::Asset::Wobject::Survey::ResponseJSON->new( $self->surveyJSON, $jsonHash ); -} ## end sub loadResponseJSON - -#------------------------------------------------------------------- - =head3 saveResponseJSON Turns the response object into JSON and saves it to the DB. @@ -1276,25 +1285,8 @@ Turns the response object into JSON and saves it to the DB. sub saveResponseJSON { my $self = shift; - my $data = $self->responseJSON->freeze(); - - $self->session->db->write( "update Survey_response set responseJSON = ? where Survey_responseId = ?", - - [ $data, $self->{responseId} ] ); -} - -#------------------------------------------------------------------- - -=head2 responseJSON - -Accessor for the ResponseJSON object - -=cut - -sub responseJSON { - my $self = shift; - return $self->{_responseJSON}; + $self->session->db->write( "update Survey_response set responseJSON = ? where Survey_responseId = ?", [ $data, $self->{responseId} ] ); } #------------------------------------------------------------------- @@ -1309,22 +1301,22 @@ If the user is anonymous, the IP is used. Or an email'd or linked code can be u sub getResponseId { my $self = shift; my %opts = validate(@_, { noCookie => 0 } ); # This is a hack to allow for testing (cookies cause problems) - + return $self->{responseId} if ( defined $self->{responseId} ); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); - my $anonId = $self->session->form->process("userid"); + my $anonId = $self->session->form->process('userid'); unless ($opts{noCookie}) { - $anonId ||= $self->session->http->getCookies->{"Survey2AnonId"}; + $anonId ||= $self->session->http->getCookies->{Survey2AnonId}; } $anonId ||= undef; unless ($opts{noCookie}) { - $self->session->http->setCookie( "Survey2AnonId", $anonId ) if ($anonId); + $self->session->http->setCookie( Survey2AnonId => $anonId ) if ($anonId); } my $responseId; @@ -1386,21 +1378,21 @@ sub getResponseId { anonId => $anonId } ); -# $self->session->log->warn("post: $responseId"); - $self->loadBothJSON($responseId); +# $self->loadBothJSON($responseId); +# $self->responseJSON(undef, $responseId); # $self->responseJSON->createSurveyOrder(); $self->{responseId} = $responseId; $self->saveResponseJSON(); - } ## end if ( $haveTaken < $allowedTakes) + } else { $self->session->log->debug("haveTaken ($haveTaken) >= allowedTakes ($allowedTakes)"); } - } ## end if ( !$responseId ) + } $self->{responseId} = $responseId; - $self->loadBothJSON($responseId); +# $self->loadBothJSON($responseId); return $responseId; -} ## end sub getResponseId +} #------------------------------------------------------------------- @@ -1685,7 +1677,7 @@ sub loadTempReportTable { [ $self->getId() ] ); $self->session->db->write( "delete from Survey_tempReport where assetId = ?", [ $self->getId() ] ); for my $ref (@$refs) { - $self->loadResponseJSON( undef, $ref->{Survey_responseId} ); + $self->responseJSON( undef, $ref->{Survey_responseId} ); my $count = 1; for my $q ( @{ $self->responseJSON->returnResponseForReporting() } ) { if ( @{ $q->{answers} } == 0 and $q->{comment} =~ /\w/ ) { @@ -1712,11 +1704,6 @@ sub loadTempReportTable { } ## end for my $q ( @{ $self->responseJSON... } ## end for my $ref (@$refs) return 1; -} ## end sub loadTempReportTable - -sub log { - my $self = shift; - $self->session->log->debug(shift); } 1; From 7128a3d07c80861b5a560db776d1ae23e38e41ce Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:27:31 +0000 Subject: [PATCH 66/90] More Survey.pm refactoring Turned getResponseId into a lazy-loading responseId mutator Added responseIdCookies flag as a workaround for WebGUI::Test::getPage's lack of cookie support --- lib/WebGUI/Asset/Wobject/Survey.pm | 257 +++++++++++++++-------------- t/Asset/Wobject/Survey.t | 3 +- 2 files changed, 139 insertions(+), 121 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 896d912cb..a290d2cb5 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -334,7 +334,7 @@ sub responseJSON { my ($json, $responseId) = validate_pos(@_, { type => SCALAR | UNDEF, optional => 1 }, { type => SCALAR, optional => 1}); if (!defined $responseId) { - $responseId = $self->{responseId}; + $responseId = $self->responseId; } if (!$self->{_responseJSON} || $json) { @@ -443,8 +443,8 @@ sub www_jumpTo { $self->session->db->write( 'delete from Survey_response where assetId = ? and userId = ?', [ $self->getId, $self->session->user->userId() ] ); - # Create a new response (and trigger loadBothJSON()) - $self->getResponseId(); + # Create a new response +# $self->responseId(); # Break the $id down into sIndex and qIndex my ($sIndex, $qIndex) = split /-/, $id; @@ -946,7 +946,7 @@ sub www_takeSurvey { my %var; eval { - my $responseId = $self->getResponseId(); + my $responseId = $self->responseId(); if ( !$responseId ) { $self->session->log->debug('No responseId, surveyEnd'); @@ -1015,7 +1015,7 @@ sub www_submitQuestions { return $self->surveyEnd(); } - my $responseId = $self->getResponseId(); + my $responseId = $self->responseId(); if ( !$responseId ) { $self->session->log->debug('No response id, surveyEnd'); return $self->surveyEnd(); @@ -1026,8 +1026,6 @@ sub www_submitQuestions { my @goodResponses = keys %$responses; #load everything. -# $self->loadBothJSON(); - my $termInfo = $self->responseJSON->recordResponses( $responses ); $self->saveResponseJSON(); @@ -1088,7 +1086,7 @@ sub www_loadQuestions { return $self->surveyEnd(); } - my $responseId = $self->getResponseId(); #also loads the survey and response + my $responseId = $self->responseId(); #also loads the survey and response if ( !$responseId ) { $self->session->log->debug('No responseId, surveyEnd'); return $self->surveyEnd(); @@ -1144,7 +1142,7 @@ sub surveyEnd { $completeCode = defined $completeCode ? $completeCode : 1; - if ( my $responseId = $self->getResponseId() ) { #also loads the survey and response + if ( my $responseId = $self->responseId() ) { #also loads the survey and response # $self->session->db->write("update Survey_response set endDate = ? and isComplete > 0 where Survey_responseId = ?",[WebGUI::DateTime->now->toDatabase,$responseId]); $self->session->db->setRow( "Survey_response", @@ -1250,30 +1248,30 @@ sub prepareShowSurveyTemplate { return to_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); } ## end sub prepareShowSurveyTemplate -#------------------------------------------------------------------- - -=head2 loadBothJSON($rId) - -Loads both the Survey and the appropriate response objects from JSON. - -=head3 $rId - -The reponse id to load. - -=cut - -sub loadBothJSON { - my $self = shift; - my $rId = shift; -# if ( defined $self->surveyJSON and defined $self->responseJSON ) { return; } - my $ref = $self->session->db->buildArrayRefOfHashRefs( " - select s.surveyJSON,r.responseJSON - from Survey s, Survey_response r - where s.assetId = ? and r.Survey_responseId = ?", - [ $self->getId, $rId ] ); - $self->surveyJSON( $ref->[0]->{surveyJSON} ); - $self->responseJSON( $ref->[0]->{responseJSON}, $rId ); -} +##------------------------------------------------------------------- +# +#=head2 loadBothJSON($rId) +# +#Loads both the Survey and the appropriate response objects from JSON. +# +#=head3 $rId +# +#The reponse id to load. +# +#=cut +# +#sub loadBothJSON { +# my $self = shift; +# my $rId = shift; +## if ( defined $self->surveyJSON and defined $self->responseJSON ) { return; } +# my $ref = $self->session->db->buildArrayRefOfHashRefs( " +# select s.surveyJSON,r.responseJSON +# from Survey s, Survey_response r +# where s.assetId = ? and r.Survey_responseId = ?", +# [ $self->getId, $rId ] ); +# $self->surveyJSON( $ref->[0]->{surveyJSON} ); +# $self->responseJSON( $ref->[0]->{responseJSON}, $rId ); +#} #------------------------------------------------------------------- @@ -1286,112 +1284,131 @@ Turns the response object into JSON and saves it to the DB. sub saveResponseJSON { my $self = shift; my $data = $self->responseJSON->freeze(); - $self->session->db->write( "update Survey_response set responseJSON = ? where Survey_responseId = ?", [ $data, $self->{responseId} ] ); + $self->session->db->write( 'update Survey_response set responseJSON = ? where Survey_responseId = ?', [ $data, $self->responseId ] ); + return; } #------------------------------------------------------------------- -=head2 getResponseId +=head2 responseId -Determines the response id of the current user. If there is not a response for the user, a new one is created. -If the user is anonymous, the IP is used. Or an email'd or linked code can be used. +Mutator for the responseIdCookies that determines whether cookies are used as +part of the L<"responseId"> lookup process. + +Useful for disabling cookie operations during tests, since WebGUI::Test::getPage +currently does not support cookies. =cut -sub getResponseId { +sub responseIdCookies { my $self = shift; - my %opts = validate(@_, { noCookie => 0 } ); # This is a hack to allow for testing (cookies cause problems) - - return $self->{responseId} if ( defined $self->{responseId} ); - - my $ip = $self->session->env->getIp; - my $id = $self->session->user->userId(); - - my $anonId = $self->session->form->process('userid'); + my ($x) = validate_pos(@_, {type => SCALAR, optional => 1}); - unless ($opts{noCookie}) { - $anonId ||= $self->session->http->getCookies->{Survey2AnonId}; + if (defined $x) { + $self->{_responseIdCookies} = $x; } - $anonId ||= undef; + # Defaults to true.. + return defined $self->{_responseIdCookies} ? $self->{_responseIdCookies} : 1; +} + +#------------------------------------------------------------------- + +=head2 responseId + +Accessor for the responseId property, which is the unique identifier for a single +L instance. See also L<"responseJSON">. + +The responseId of the current user is returned, or created if one does not already exist. +If the user is anonymous, the IP is used. Or an emailed or linked code can be used. + +=cut + +sub responseId { + my $self = shift; + + if (!defined $self->{responseId}) { - unless ($opts{noCookie}) { - $self->session->http->setCookie( Survey2AnonId => $anonId ) if ($anonId); - } - - my $responseId; - - my $string; - - #if there is an anonid or id is for a WG user - if ( $anonId or $id != 1 ) { - $string = 'userId'; - if ($anonId) { - $string = 'anonId'; - $id = $anonId; + my $ip = $self->session->env->getIp; + my $id = $self->session->user->userId; + my $anonId = $self->session->form->process('userid'); + if ($self->responseIdCookies) { + $anonId ||= $self->session->http->getCookies->{Survey2AnonId}; ## no critic } - $responseId - = $self->session->db->quickScalar( - "select Survey_responseId from Survey_response where $string = ? and assetId = ? and isComplete = 0", - [ $id, $self->getId() ] ); - - } - elsif ( $id == 1 ) { - $responseId = $self->session->db->quickScalar( - "select Survey_responseId from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = 0", - [ $id, $ip, $self->getId() ] - ); - } - - if ( !$responseId ) { - my $allowedTakes - = $self->session->db->quickScalar( - "select maxResponsesPerUser from Survey where assetId = ? order by revisionDate desc limit 1", - [ $self->getId() ] ); - my $haveTaken; - - if ( $id == 1 ) { - $haveTaken - = $self->session->db->quickScalar( - "select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ?", - [ $id, $ip, $self->getId() ] ); + $anonId ||= undef; + + if ($self->responseIdCookies) { + $anonId && $self->session->http->setCookie( Survey2AnonId => $anonId ); } - else { - $haveTaken + + my ($responseId, $string); + + # if there is an anonid or id is for a WG user + if ( $anonId or $id != 1 ) { + $string = 'userId'; + if ($anonId) { + $string = 'anonId'; + $id = $anonId; + } + $responseId = $self->session->db->quickScalar( - "select count(*) from Survey_response where $string = ? and assetId = ?", + "select Survey_responseId from Survey_response where $string = ? and assetId = ? and isComplete = 0", [ $id, $self->getId() ] ); + } - - if ( $haveTaken < $allowedTakes ) { - my $time = time(); - $responseId = $self->session->db->setRow( - "Survey_response", - "Survey_responseId", { - Survey_responseId => "new", - userId => $id, - ipAddress => $ip, - username => $self->session->user->username, - startDate => $time, #WebGUI::DateTime->now->toDatabase, - endDate => 0, #WebGUI::DateTime->now->toDatabase, - assetId => $self->getId(), - anonId => $anonId - } + elsif ( $id == 1 ) { + $responseId = $self->session->db->quickScalar( + 'select Survey_responseId from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = 0', + [ $id, $ip, $self->getId() ] ); -# $self->loadBothJSON($responseId); -# $self->responseJSON(undef, $responseId); -# $self->responseJSON->createSurveyOrder(); - $self->{responseId} = $responseId; - $self->saveResponseJSON(); + } + + if ( !$responseId ) { + my $allowedTakes + = $self->session->db->quickScalar( + 'select maxResponsesPerUser from Survey where assetId = ? order by revisionDate desc limit 1', + [ $self->getId() ] ); + my $haveTaken; + + if ( $id == 1 ) { + $haveTaken + = $self->session->db->quickScalar( + 'select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ?', + [ $id, $ip, $self->getId() ] ); + } + else { + $haveTaken + = $self->session->db->quickScalar( + "select count(*) from Survey_response where $string = ? and assetId = ?", + [ $id, $self->getId() ] ); + } + + if ( $haveTaken < $allowedTakes ) { + $responseId = $self->session->db->setRow( + 'Survey_response', + 'Survey_responseId', { + Survey_responseId => 'new', + userId => $id, + ipAddress => $ip, + username => $self->session->user->username, + startDate => scalar time, #WebGUI::DateTime->now->toDatabase, + endDate => 0, #WebGUI::DateTime->now->toDatabase, + assetId => $self->getId(), + anonId => $anonId + } + ); + # Store the newly created responseId and then persist ResponseJSON + $self->{responseId} = $responseId; + $self->saveResponseJSON(); + } + else { + $self->session->log->debug("haveTaken ($haveTaken) >= allowedTakes ($allowedTakes)"); + } } - else { - $self->session->log->debug("haveTaken ($haveTaken) >= allowedTakes ($allowedTakes)"); - } + $self->{responseId} = $responseId; } - $self->{responseId} = $responseId; -# $self->loadBothJSON($responseId); - return $responseId; + return $self->{responseId}; } #------------------------------------------------------------------- @@ -1407,7 +1424,7 @@ sub canTakeSurvey { return $self->{canTake} if ( defined $self->{canTake} ); - if ( !$self->session->user->isInGroup( $self->get("groupToTakeSurvey") ) ) { + if ( !$self->session->user->isInGroup( $self->get('groupToTakeSurvey') ) ) { return 0; } @@ -1426,7 +1443,7 @@ sub canTakeSurvey { else { $takenCount = $self->session->db->quickScalar( - "select count(*) from Survey_response where userId = ? and assetId = ? and isComplete > ?", + 'select count(*) from Survey_response where userId = ? and assetId = ? and isComplete > ?', [ $id, $self->getId(), 0 ] ); } @@ -1438,7 +1455,7 @@ sub canTakeSurvey { } return $self->{canTake}; -} ## end sub canTakeSurvey +} #------------------------------------------------------------------- diff --git a/t/Asset/Wobject/Survey.t b/t/Asset/Wobject/Survey.t index ba75ee52c..dff69f175 100644 --- a/t/Asset/Wobject/Survey.t +++ b/t/Asset/Wobject/Survey.t @@ -61,7 +61,8 @@ $survey->saveSurveyJSON(); # Now start a response as admin user $session->user( { userId =>3 } ); -$survey->getResponseId( { noCookie => 1 }); # triggers loadBothJSON() +$survey->responseIdCookies(0); +#$survey->responseId( { noCookie => 1 }); # triggers loadBothJSON() #for my $address (@{ $survey->responseJSON->surveyOrder }) { # diag (Dumper $address); From 5a2f514a5a7e63336cb2d8d92a9fdd7447646b25 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:27:54 +0000 Subject: [PATCH 67/90] Added Delete Responses back into Survey template (with i18n) --- lib/WebGUI/Asset/Wobject/Survey.pm | 58 ++++++------------------ lib/WebGUI/i18n/English/Asset_Survey.pm | 4 ++ root_import_survey.wgpkg | Bin 10490 -> 10634 bytes 3 files changed, 17 insertions(+), 45 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index a290d2cb5..977128d42 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -443,9 +443,6 @@ sub www_jumpTo { $self->session->db->write( 'delete from Survey_response where assetId = ? and userId = ?', [ $self->getId, $self->session->user->userId() ] ); - # Create a new response -# $self->responseId(); - # Break the $id down into sIndex and qIndex my ($sIndex, $qIndex) = split /-/, $id; @@ -843,6 +840,7 @@ sub getMenuVars { $var{'edit_survey_url'} = $self->getUrl('func=editSurvey'); $var{'take_survey_url'} = $self->getUrl('func=takeSurvey'); + $var{'delete_responses_url'} = $self->getUrl('func=deleteResponses'); $var{'view_simple_results_url'} = $self->getUrl('func=exportSimpleResults'); $var{'view_transposed_results_url'} = $self->getUrl('func=exportTransposedResults'); $var{'view_statistical_overview_url'} = $self->getUrl('func=viewStatisticalOverview'); @@ -930,61 +928,31 @@ See WebGUI::Asset::Wobject::www_view() for details. sub www_view { my $self = shift; - $self->SUPER::www_view(@_); + return $self->SUPER::www_view(@_); } #------------------------------------------------------------------- =head2 www_takeSurvey -Returns the template needed to take the survey. This template dynamically loads the survey via async requests. +The take survey page does very little. It is a simple shell (controlled by surveyTakeTemplateId). + +Survey questions are loaded asynchronously via javascript calls to L<"www_loadQuestions">. =cut sub www_takeSurvey { my $self = shift; - my %var; - - eval { - my $responseId = $self->responseId(); - if ( !$responseId ) { - $self->session->log->debug('No responseId, surveyEnd'); - - # return $self->surveyEnd(); # disabled. let the js handle the exitUrl redirection - } - else { - $self->session->log->debug("ResponseId: $responseId"); - } - }; - - $self->session->style->setScript($self->session->url->extras('yui/build/utilities/utilities.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/container/container-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/menu/menu-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/button/button-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/calendar/calendar-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/json/json-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/logger/logger-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/resize/resize-min.js'), {type => - 'text/javascript'}); - $self->session->style->setScript($self->session->url->extras('yui/build/slider/slider-min.js'), {type => - 'text/javascript'}); - - my $out = $self->processTemplate( \%var, $self->get("surveyTakeTemplateId") ); - return $self->session->style->process( $out, $self->get("styleTemplateId") ); -} ## end sub www_takeSurvey + + my $out = $self->processTemplate( {}, $self->get('surveyTakeTemplateId') ); + return $self->session->style->process( $out, $self->get('styleTemplateId') ); +} #------------------------------------------------------------------- =head2 www_deleteResponses -Deletes all the responses from the survey. +Deletes all responses from this survey instance. =cut @@ -992,7 +960,7 @@ sub www_deleteResponses { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); $self->session->db->write( 'delete from Survey_response where assetId = ?', [ $self->getId ] ); @@ -1086,7 +1054,7 @@ sub www_loadQuestions { return $self->surveyEnd(); } - my $responseId = $self->responseId(); #also loads the survey and response + my $responseId = $self->responseId(); if ( !$responseId ) { $self->session->log->debug('No responseId, surveyEnd'); return $self->surveyEnd(); @@ -1142,7 +1110,7 @@ sub surveyEnd { $completeCode = defined $completeCode ? $completeCode : 1; - if ( my $responseId = $self->responseId() ) { #also loads the survey and response + if ( my $responseId = $self->responseId ) { # $self->session->db->write("update Survey_response set endDate = ? and isComplete > 0 where Survey_responseId = ?",[WebGUI::DateTime->now->toDatabase,$responseId]); $self->session->db->setRow( "Survey_response", diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index 233a9b58a..2c61b4611 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -31,6 +31,10 @@ our $I18N = { message => q|View Grade Book|, lastUpdated => 1224686319 }, + 'delete responses' => { + message => q|Delete Responses|, + lastUpdated => 0 + }, 'continue button' => { message => q|Continue|, lastUpdated => 1224686319 diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg index 8691beeb78149fe16c6401068f67b19b7025deb4..21d1c03d54eefb29f91e211b5924333bc1f1802f 100644 GIT binary patch delta 10253 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 T2MXj<;Qs*#vulr}00aR5IDyTp delta 10036 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 Kfz==Y0s#OV8jz9z From d9316f47c4680d6e9e20507bc25ef129afc39b86 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Feb 2009 09:28:16 +0000 Subject: [PATCH 68/90] Made perlcritic a little less grumpy about Survey.pm --- lib/WebGUI/Asset/Wobject/Survey.pm | 423 ++++++++++++++--------------- 1 file changed, 203 insertions(+), 220 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 977128d42..7bb065593 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -487,6 +487,10 @@ Takes the address of a survey object and creates a copy. The copy is placed at Returns the address to the new object. +=head3 $address + +See L + =cut sub copyObject { @@ -512,9 +516,7 @@ Returns the address to the parent object, or the very first section. =head3 $address -An array ref. The first element of the array ref is the index of -the section. The second element is the index of the question in -that section. The third element is the index of the answer. +See L =cut @@ -549,11 +551,11 @@ sub www_newObject { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); my $ref; - my $ids = $self->session->form->process("data"); + my $ids = $self->session->form->process('data'); my @inAddress = split /-/, $ids; @@ -578,9 +580,9 @@ sub www_dragDrop { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get('groupToEditSurvey') ) ); + if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - my $p = from_json( $self->session->form->process("data") ); + my $p = from_json( $self->session->form->process('data') ); my @tid = split /-/, $p->{target}->{id}; my @bid = split /-/, $p->{before}->{id}; @@ -636,7 +638,7 @@ sub www_dragDrop { $self->saveSurveyJSON(); return $self->www_loadSurvey( { address => $address } ); -} ## end sub www_dragDrop +} #------------------------------------------------------------------- @@ -657,13 +659,14 @@ sub www_loadSurvey { my $address = defined $options->{address} ? $options->{address} : undef; if ( !defined $address ) { - if ( my $inAddress = $self->session->form->process("data") ) { - if( $inAddress eq '-' ) { - $editflag = 0; - $address = [ 0 ]; - } else { - $address = [ split /-/, $inAddress ]; - } + if ( my $inAddress = $self->session->form->process('data') ) { + if ( $inAddress eq q{-} ) { + $editflag = 0; + $address = [0]; + } + else { + $address = [ split /-/, $inAddress ]; + } } else { $address = [0]; @@ -676,22 +679,22 @@ sub www_loadSurvey { my $editHtml; if ( $var->{type} eq 'section' ) { - $editHtml = $self->processTemplate( $var, $self->get("sectionEditTemplateId") ); + $editHtml = $self->processTemplate( $var, $self->get('sectionEditTemplateId') ); } elsif ( $var->{type} eq 'question' ) { - $editHtml = $self->processTemplate( $var, $self->get("questionEditTemplateId") ); + $editHtml = $self->processTemplate( $var, $self->get('questionEditTemplateId') ); } elsif ( $var->{type} eq 'answer' ) { - $editHtml = $self->processTemplate( $var, $self->get("answerEditTemplateId") ); + $editHtml = $self->processTemplate( $var, $self->get('answerEditTemplateId') ); } # Generate the list of valid goto targets my @gotoTargets = $self->surveyJSON->getGotoTargets; my %buttons; - $buttons{question} = $$address[0]; - if ( @$address == 2 or @$address == 3 ) { - $buttons{answer} = "$$address[0]-$$address[1]"; + $buttons{question} = $address->[0]; + if ( @{$address} == 2 or @{$address} == 3 ) { + $buttons{answer} = "$address->[0]-$address->[1]"; } my $data = $self->surveyJSON->getDragDropList($address); @@ -702,7 +705,7 @@ sub www_loadSurvey { my @ids; my ( $s, $q, $a ) = ( 0, 0, 0 ); #bools on if a button has already been created - foreach (@$data) { + foreach (@{$data}) { if ( $_->{type} eq 'section' ) { $lastId{section} = ++$scount; if ( $lastType eq 'answer' ) { @@ -720,33 +723,35 @@ sub www_loadSurvey { $a = 1; } $html .= "
  • Q" . ( $qcount + 1 ) . ": $_->{text}<\/li>
    \n"; - push( @ids, "$scount-$qcount" ); + push @ids, "$scount-$qcount"; $lastType = 'question'; $acount = -1; } elsif ( $_->{type} eq 'answer' ) { $lastId{answer} = ++$acount; - $html .= "
  • A" . ( $acount + 1 ) . ": $_->{text}<\/li>
    \n"; - push( @ids, "$scount-$qcount-$acount" ); + $html + .= "
  • A" + . ( $acount + 1 ) + . ": $_->{text}<\/li>
    \n"; + push @ids, "$scount-$qcount-$acount"; $lastType = 'answer'; } - } ## end foreach (@$data) + } - #address is the address of the focused object - #buttons are the data to create the Add buttons - #edithtml is the html edit the object - #ddhtml is the html to create the draggable html divs - #ids is a list of all ids passed in which are draggable (for adding events) - #type is the object type my $return = { - "address", $address, "buttons", \%buttons, - "edithtml", $editflag ? $editHtml : '', - "ddhtml", $html, "ids", \@ids, "type", $var->{type} - ,gotoTargets => \@gotoTargets, + address => $address, # the address of the focused object + buttons => \%buttons, # the data to create the Add buttons + edithtml => $editflag ? $editHtml : q{}, # the html edit the object + ddhtml => $html, # the html to create the draggable html divs + ids => \@ids, # list of all ids passed in which are draggable (for adding events) + type => $var->{type}, # the object type + gotoTargets => \@gotoTargets, }; + $self->session->http->setMimeType('application/json'); + return to_json($return); -} ## end sub www_loadSurvey +} #------------------------------------------------------------------- @@ -759,13 +764,14 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; $self->SUPER::prepareView(); - my $templateId = $self->get("templateId"); - if ( $self->session->form->process("overrideTemplateId") ne "" ) { - $templateId = $self->session->form->process("overrideTemplateId"); + my $templateId = $self->get('templateId'); + if ( $self->session->form->process('overrideTemplateId') ne q{} ) { + $templateId = $self->session->form->process('overrideTemplateId'); } my $template = WebGUI::Asset::Template->new( $self->session, $templateId ); $template->prepare; $self->{_viewTemplate} = $template; + return; } #------------------------------------------------------------------- @@ -778,9 +784,9 @@ Completely remove from WebGUI. sub purge { my $self = shift; - $self->session->db->write( "delete from Survey_response where assetId = ?", [ $self->getId() ] ); - $self->session->db->write( "delete from Survey_tempReport where assetId = ?", [ $self->getId() ] ); - $self->session->db->write( "delete from Survey where assetId = ?", [ $self->getId() ] ); + $self->session->db->write( 'delete from Survey_response where assetId = ?', [ $self->getId() ] ); + $self->session->db->write( 'delete from Survey_tempReport where assetId = ?', [ $self->getId() ] ); + $self->session->db->write( 'delete from Survey where assetId = ?', [ $self->getId() ] ); return $self->SUPER::purge; } @@ -794,14 +800,8 @@ See WebGUI::Asset::purgeCache() for details. sub purgeCache { my $self = shift; - WebGUI::Cache->new( $self->session, "view_" . $self->getId )->delete; - $self->SUPER::purgeCache; -} - -#------------------------------------------------------------------- -sub purgeRevision { - my $self = shift; - return $self->SUPER::purgeRevision; + WebGUI::Cache->new( $self->session, 'view_' . $self->getId )->delete; + return $self->SUPER::purgeCache; } #------------------------------------------------------------------- @@ -824,7 +824,7 @@ sub view { my $out = $self->processTemplate( $var, undef, $self->{_viewTemplate} ); return $out; -} ## end sub view +} #------------------------------------------------------------------- @@ -845,9 +845,9 @@ sub getMenuVars { $var{'view_transposed_results_url'} = $self->getUrl('func=exportTransposedResults'); $var{'view_statistical_overview_url'} = $self->getUrl('func=viewStatisticalOverview'); $var{'view_grade_book_url'} = $self->getUrl('func=viewGradeBook'); - $var{'user_canTakeSurvey'} = $self->session->user->isInGroup( $self->get("groupToTakeSurvey") ); - $var{'user_canViewReports'} = $self->session->user->isInGroup( $self->get("groupToViewReports") ); - $var{'user_canEditSurvey'} = $self->session->user->isInGroup( $self->get("groupToEditSurvey") ); + $var{'user_canTakeSurvey'} = $self->session->user->isInGroup( $self->get('groupToTakeSurvey') ); + $var{'user_canViewReports'} = $self->session->user->isInGroup( $self->get('groupToViewReports') ); + $var{'user_canEditSurvey'} = $self->session->user->isInGroup( $self->get('groupToEditSurvey') ); return \%var; } @@ -866,13 +866,13 @@ sub getResponseInfoForView { my ( $code, $taken ); - my $maxTakes = $self->getValue("maxResponsesPerUser"); + my $maxTakes = $self->getValue('maxResponsesPerUser'); my $id = $self->session->user->userId(); my $anonId - = $self->session->form->process("userid") - || $self->session->http->getCookies->{"Survey2AnonId"} + = $self->session->form->process('userid') + || $self->session->http->getCookies->{Survey2AnonId} || undef; - $self->session->http->setCookie( "Survey2AnonId", $anonId ) if ($anonId); + $anonId && $self->session->http->setCookie( Survey2AnonId => $anonId ); my $ip = $self->session->env->getIp; my $string; @@ -898,37 +898,24 @@ sub getResponseInfoForView { "select count(*) from Survey_response where $string = ? and assetId = ? and isComplete > 0", [ $id, $self->getId() ] ); - } ## end if ( $anonId or $id !=... + } elsif ( $id == 1 ) { my $responseId = $self->session->db->quickScalar( - "select Survey_responseId from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = 0", + 'select Survey_responseId from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete = 0', [ $id, $ip, $self->getId() ] ); if ( !$responseId ) { $code = $self->session->db->quickScalar( - "select isComplete from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete > 0 order by endDate desc limit 1", + 'select isComplete from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete > 0 order by endDate desc limit 1', [ $id, $ip, $self->getId() ] ); } $taken = $self->session->db->quickScalar( - "select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete > 0", + 'select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ? and isComplete > 0', [ $id, $ip, $self->getId() ] ); - } ## end elsif ( $id == 1 ) + } return ( $code, $taken >= $maxTakes ); -} ## end sub getResponseInfoForView - -#------------------------------------------------------------------- - -=head2 www_view ( ) - -See WebGUI::Asset::Wobject::www_view() for details. - -=cut - -sub www_view { - my $self = shift; - return $self->SUPER::www_view(@_); } #------------------------------------------------------------------- @@ -990,9 +977,9 @@ sub www_submitQuestions { } my $responses = $self->session->form->paramsHashRef(); - delete $$responses{'func'}; + delete $responses->{func}; - my @goodResponses = keys %$responses; #load everything. + my @goodResponses = keys %{$responses}; #load everything. my $termInfo = $self->responseJSON->recordResponses( $responses ); @@ -1005,37 +992,37 @@ sub www_submitQuestions { return $self->www_loadQuestions(); - my $files = 0; - - # for my $id(@$orderOf){ - #if a file upload, write to disk - # my $path; - # if($id->{'questionType'} eq 'File Upload'){ - # $files = 1; - # my $storage = WebGUI::Storage->create($self->session); - # my $filename = $storage->addFileFromFormPost( $id->{'Survey_answerId'} ); - # $path = $storage->getPath($filename); - # } - #$self->session->errorHandler->error("Inserting a response ".$id->{'Survey_answerId'}." $responseId, $path, ".$$responses{$id->{'Survey_answerId'}}); - # $self->session->db->write("insert into Survey_questionResponse - # select ?, Survey_sectionId, Survey_questionId, Survey_answerId, ?, ?, ?, now(), ?, ? from Survey_answer where Survey_answerId = ?", - # [$self->getId(), $responseId, $$responses{ $id->{'Survey_answerId'} }, '', $path, ++$lastOrder, $id->{'Survey_answerId'}]); - # } - if ($files) { - ##special case, need to check for more questions in section, if not, more current up one - my $lastA = $self->getLastAnswerInfo($responseId); - my $questionId = $self->getNextQuestionId( $lastA->{'Survey_questionId'} ); - if ( !$questionId ) { - my $currentSection = $self->getCurrentSection($responseId); - $currentSection = $self->getNextSection($currentSection); - if ($currentSection) { - $self->setCurrentSection( $responseId, $currentSection ); - } - } - return; - } - return $self->www_loadQuestions($responseId); -} ## end sub www_submitQuestions +# my $files = 0; +# +# # for my $id(@$orderOf){ +# #if a file upload, write to disk +# # my $path; +# # if($id->{'questionType'} eq 'File Upload'){ +# # $files = 1; +# # my $storage = WebGUI::Storage->create($self->session); +# # my $filename = $storage->addFileFromFormPost( $id->{'Survey_answerId'} ); +# # $path = $storage->getPath($filename); +# # } +# #$self->session->errorHandler->error("Inserting a response ".$id->{'Survey_answerId'}." $responseId, $path, ".$$responses{$id->{'Survey_answerId'}}); +# # $self->session->db->write("insert into Survey_questionResponse +# # select ?, Survey_sectionId, Survey_questionId, Survey_answerId, ?, ?, ?, now(), ?, ? from Survey_answer where Survey_answerId = ?", +# # [$self->getId(), $responseId, $$responses{ $id->{'Survey_answerId'} }, '', $path, ++$lastOrder, $id->{'Survey_answerId'}]); +# # } +# if ($files) { +# ##special case, need to check for more questions in section, if not, more current up one +# my $lastA = $self->getLastAnswerInfo($responseId); +# my $questionId = $self->getNextQuestionId( $lastA->{'Survey_questionId'} ); +# if ( !$questionId ) { +# my $currentSection = $self->getCurrentSection($responseId); +# $currentSection = $self->getNextSection($currentSection); +# if ($currentSection) { +# $self->setCurrentSection( $responseId, $currentSection ); +# } +# } +# return; +# } +# return $self->www_loadQuestions($responseId); +} #------------------------------------------------------------------- @@ -1080,12 +1067,10 @@ sub www_loadQuestions { my $text = $self->prepareShowSurveyTemplate( $section, \@questions ); return $text; -} ## end sub www_loadQuestions +} #------------------------------------------------------------------- -#called when the survey is over. - =head2 surveyEnd ( [ $url ], [ $completeCode ] ) Marks the survey completed with either 1 or the $completeCode and then sends the url to the site home or if defined, $url. @@ -1113,38 +1098,38 @@ sub surveyEnd { if ( my $responseId = $self->responseId ) { # $self->session->db->write("update Survey_response set endDate = ? and isComplete > 0 where Survey_responseId = ?",[WebGUI::DateTime->now->toDatabase,$responseId]); $self->session->db->setRow( - "Survey_response", - "Survey_responseId", { + 'Survey_response', + 'Survey_responseId', { Survey_responseId => $responseId, - endDate => time(), #WebGUI::DateTime->now->toDatabase, + endDate => scalar time, #WebGUI::DateTime->now->toDatabase, isComplete => $completeCode } ); } if ($self->get('doAfterTimeLimit') eq 'restartSurvey' && $completeCode == 2){ - $self->responseJSON->startTime(time()); + $self->responseJSON->startTime(scalar time); undef $self->{_responseJSON}; undef $self->{responseId}; return $self->www_loadQuestions('1'); - }else{ + } else { if ( $url !~ /\w/ ) { $url = 0; } - if ( $url eq "undefined" ) { $url = 0; } + if ( $url eq 'undefined' ) { $url = 0; } if ( !$url ) { $url = $self->session->db->quickScalar( - "select exitURL from Survey where assetId = ? order by revisionDate desc limit 1", + 'select exitURL from Survey where assetId = ? order by revisionDate desc limit 1', [ $self->getId() ] ); if ( !$url ) { - $url = "/"; + $url = q{/}; } } } $url = $self->session->url->gateway($url); #$self->session->http->setRedirect($url); #$self->session->http->setMimeType('application/json'); - my $json = to_json( { "type", "forward", "url", $url } ); + my $json = to_json( { type => 'forward', url => $url } ); return $json; -} ## end sub surveyEnd +} #------------------------------------------------------------------- @@ -1171,50 +1156,50 @@ sub prepareShowSurveyTemplate { my %hidden = ( 'Hidden', 1 ); foreach my $q (@$questions) { - if ( $fileUpload{ $$q{'questionType'} } ) { $q->{'fileLoader'} = 1; } - elsif ( $text{ $$q{'questionType'} } ) { $q->{'textType'} = 1; } - elsif ( $textArea{ $$q{'questionType'} } ) { $q->{'textAreaType'} = 1; } - elsif ( $hidden{ $$q{'questionType'} } ) { $q->{'hidden'} = 1; } - elsif ( $multipleChoice{ $$q{'questionType'} } ) { - $q->{'multipleChoice'} = 1; - if ( $$q{'maxAnswers'} > 1 ) { - $q->{'maxMoreOne'} = 1; + if ( $fileUpload{ $q->{questionType} } ) { $q->{fileLoader} = 1; } + elsif ( $text{ $q->{questionType} } ) { $q->{textType} = 1; } + elsif ( $textArea{ $q->{questionType} } ) { $q->{textAreaType} = 1; } + elsif ( $hidden{ $q->{questionType} } ) { $q->{hidden} = 1; } + elsif ( $multipleChoice{ $q->{questionType} } ) { + $q->{multipleChoice} = 1; + if ( $q->{maxAnswers} > 1 ) { + $q->{maxMoreOne} = 1; } } - elsif ( $dateType{ $$q{'questionType'} } ) { - $q->{'dateType'} = 1; + elsif ( $dateType{ $q->{questionType} } ) { + $q->{dateType} = 1; } - elsif ( $slider{ $$q{'questionType'} } ) { - $q->{'slider'} = 1; - if ( $$q{'questionType'} eq 'Dual Slider - Range' ) { - $q->{'dualSlider'} = 1; - $q->{'a1'} = [ $q->{'answers'}->[0] ]; - $q->{'a2'} = [ $q->{'answers'}->[1] ]; + elsif ( $slider{ $q->{questionType} } ) { + $q->{slider} = 1; + if ( $q->{questionType} eq 'Dual Slider - Range' ) { + $q->{dualSlider} = 1; + $q->{a1} = [ $q->{answers}->[0] ]; + $q->{a2} = [ $q->{answers}->[1] ]; } } - if ( $$q{'verticalDisplay'} ) { - $$q{'verts'} = "

    "; - $$q{'verte'} = "

    "; + if ( $q->{verticalDisplay} ) { + $q->{verts} = '

    '; + $q->{verte} = '

    '; } - } ## end foreach my $q (@$questions) - $section->{'questions'} = $questions; - $section->{'questionsAnswered'} = $self->responseJSON->{questionsAnswered}; - $section->{'totalQuestions'} = @{ $self->responseJSON->surveyOrder }; - $section->{'showProgress'} = $self->get('showProgress'); - $section->{'showTimeLimit'} = $self->get('showTimeLimit'); - $section->{'minutesLeft'} + } + $section->{questions} = $questions; + $section->{questionsAnswered} = $self->responseJSON->{questionsAnswered}; + $section->{totalQuestions} = @{ $self->responseJSON->surveyOrder }; + $section->{showProgress} = $self->get('showProgress'); + $section->{showTimeLimit} = $self->get('showTimeLimit'); + $section->{minutesLeft} = int( ( ( $self->responseJSON->startTime() + ( 60 * $self->get('timeLimit') ) ) - time() ) / 60 ); - if(scalar @$questions == ($section->{'totalQuestions'} - $section->{'questionsAnswered'})){ + if(scalar @{$questions} == ($section->{totalQuestions} - $section->{questionsAnswered})){ $section->{isLastPage} = 1 } - my $out = $self->processTemplate( $section, $self->get("surveyQuestionsId") ); + my $out = $self->processTemplate( $section, $self->get('surveyQuestionsId') ); $self->session->http->setMimeType('application/json'); - return to_json( { "type", "displayquestions", "section", $section, "questions", $questions, "html", $out } ); -} ## end sub prepareShowSurveyTemplate + return to_json( { type => 'displayquestions', section => $section, questions => $questions, html => $out } ); +} ##------------------------------------------------------------------- # @@ -1397,15 +1382,15 @@ sub canTakeSurvey { } #Does user have too many finished survey responses - my $maxTakes = $self->getValue("maxResponsesPerUser"); + my $maxTakes = $self->getValue('maxResponsesPerUser'); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); my $takenCount = 0; if ( $id == 1 ) { $takenCount = $self->session->db->quickScalar( - "select count(*) from Survey_response where userId = ? and ipAddress = ? and assetId = ? - and isComplete > ?", [ $id, $ip, $self->getId(), 0 ] + 'select count(*) from Survey_response where userId = ? and ipAddress = ? ' + . 'and assetId = ? and isComplete > ?', [ $id, $ip, $self->getId(), 0 ] ); } else { @@ -1438,40 +1423,40 @@ sub www_viewGradeBook { my $db = $self->session->db; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); + if !$self->session->user->isInGroup( $self->get('groupToViewReports') ); my $var = $self->getMenuVars; $self->loadTempReportTable(); my $paginator = WebGUI::Paginator->new($self->session,$self->getUrl('func=viewGradebook')); - $paginator->setDataByQuery("select userId,username,ipAddress,Survey_responseId,startDate,endDate - from Survey_response - where assetId=".$db->quote($self->getId)." order by username,ipAddress,startDate"); + $paginator->setDataByQuery('select userId,username,ipAddress,Survey_responseId,startDate,endDate' + . ' from Survey_response where assetId=' + . $db->quote($self->getId) + . ' order by username,ipAddress,startDate'); my $users = $paginator->getPageData; - $var->{question_count} = $self->surveyJSON->questionCount; + $var->{question_count} = $self->surveyJSON->questionCount; my @responseloop; - foreach my $user (@$users) { - my ($correctCount) = $db->quickArray("select count(*) from Survey_tempReport - where Survey_responseId=? and isCorrect=1",[$user->{Survey_responseId}]); - push(@responseloop, { + foreach my $user (@{$users}) { + my ($correctCount) = $db->quickArray('select count(*) from Survey_tempReport' + . ' where Survey_responseId=? and isCorrect=1',[$user->{Survey_responseId}]); + push @responseloop, { # response_url is left out because it looks like Survey doesn't have a viewIndividualSurvey feature # yet. #'response_url'=>$self->getUrl('func=viewIndividualSurvey;responseId='.$user->{Survey_responseId}), 'response_user_name'=>($user->{userId} eq '1') ? $user->{ipAddress} : $user->{username}, 'response_count_correct' => $correctCount, 'response_percent' => round(($correctCount/$var->{question_count})*100) - }); + }; } $var->{response_loop} = \@responseloop; $paginator->appendTemplateVars($var); - my $out = $self->processTemplate( $var, $self->get("gradebookTemplateId") ); - return $self->session->style->process( $out, $self->get("styleTemplateId") ); - -} ## end sub www_viewGradeBook + my $out = $self->processTemplate( $var, $self->get('gradebookTemplateId') ); + return $self->session->style->process( $out, $self->get('styleTemplateId') ); +} #------------------------------------------------------------------- @@ -1486,7 +1471,7 @@ sub www_viewStatisticalOverview { my $db = $self->session->db; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); + if !$self->session->user->isInGroup( $self->get('groupToViewReports') ); $self->loadTempReportTable(); my $survey = $self->surveyJSON; @@ -1500,13 +1485,13 @@ sub www_viewStatisticalOverview { my $questionType = $question->{questionType}; my (@answerloop, $totalResponses);; - if ($questionType eq "Multiple Choice"){ - $totalResponses = $db->quickScalar("select count(*) from Survey_tempReport - where sectionNumber=? and questionNumber=?",[$sectionIndex,$questionIndex]); + if ($questionType eq 'Multiple Choice'){ + $totalResponses = $db->quickScalar('select count(*) from Survey_tempReport' + . ' where sectionNumber=? and questionNumber=?',[$sectionIndex,$questionIndex]); for ( my $answerIndex = 0; $answerIndex <= $#{ $survey->answers([$sectionIndex,$questionIndex]) }; $answerIndex++ ) { - my $numResponses = $db->quickScalar("select count(*) from Survey_tempReport - where sectionNumber=? and questionNumber=? and answerNumber=?", + my $numResponses = $db->quickScalar('select count(*) from Survey_tempReport' + . ' where sectionNumber=? and questionNumber=? and answerNumber=?', [$sectionIndex,$questionIndex,$answerIndex]); my $responsePercent; if ($totalResponses) { @@ -1515,43 +1500,43 @@ sub www_viewStatisticalOverview { $responsePercent = 0; } my @commentloop; - my $comments = $db->read("select answerComment from Survey_tempReport - where sectionNumber=? and questionNumber=? and answerNumber=?", + my $comments = $db->read('select answerComment from Survey_tempReport' + . ' where sectionNumber=? and questionNumber=? and answerNumber=?', [$sectionIndex,$questionIndex,$answerIndex]); while (my ($comment) = $comments->array) { - push(@commentloop,{ + push @commentloop,{ 'answer_comment'=>$comment - }); + }; } - push(@answerloop,{ + push @answerloop,{ 'answer_isCorrect'=>$survey->answer( [ $sectionIndex, $questionIndex, $answerIndex ] )->{isCorrect}, 'answer' => $survey->answer( [ $sectionIndex, $questionIndex, $answerIndex ] )->{text}, 'answer_response_count' =>$numResponses, 'answer_response_percent' =>$responsePercent, 'comment_loop'=>\@commentloop - }); + }; } } else{ - my $responses = $db->read("select value,answerComment from Survey_tempReport - where sectionNumber=? and questionNumber=?", - [$sectionIndex,$questionIndex]); + my $responses = $db->read('select value,answerComment from Survey_tempReport' + . ' where sectionNumber=? and questionNumber=?', + [$sectionIndex,$questionIndex]); while (my $response = $responses->hashRef) { - push(@answerloop,{ + push @answerloop,{ 'answer_value' =>$response->{value}, 'answer_comment' =>$response->{answerComment} - }); + }; } } - push(@questionloop,{ - 'question' => $question->{text}, - 'question_id' => $sectionIndex.'_'.$questionIndex, - 'question_isMultipleChoice' => ($questionType eq "Multiple Choice"), - 'question_response_total' => $totalResponses, - 'answer_loop' => \@answerloop, - 'questionallowComment' => $question->{allowComment} - }); - } ## end for ( my $questionIndex = 0; $questionIndex <= ... + push@questionloop,{ + question => $question->{text}, + question_id => "${sectionIndex}_$questionIndex", + question_isMultipleChoice => ($questionType eq 'Multiple Choice'), + question_response_total => $totalResponses, + answer_loop => \@answerloop, + questionallowComment => $question->{allowComment} + }; + } } $paginator->setDataByArrayRef(\@questionloop); @questionloop = @{$paginator->getPageData}; @@ -1559,8 +1544,8 @@ sub www_viewStatisticalOverview { $var->{question_loop} = \@questionloop; $paginator->appendTemplateVars($var); - my $out = $self->processTemplate( $var, $self->get("overviewTemplateId") ); - return $self->session->style->process( $out, $self->get("styleTemplateId") ); + my $out = $self->processTemplate( $var, $self->get('overviewTemplateId') ); + return $self->session->style->process( $out, $self->get('styleTemplateId') ); } #------------------------------------------------------------------- @@ -1568,14 +1553,14 @@ sub www_exportSimpleResults { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); + if !$self->session->user->isInGroup( $self->get('groupToViewReports')); $self->loadTempReportTable(); - my $filename = $self->session->url->escape( $self->get("title") . "_results.tab" ); + my $filename = $self->session->url->escape( $self->get('title') . '_results.tab' ); my $content = $self->session->db->quickTab( - "select * from Survey_tempReport t where t.assetId=? order by t.Survey_responseId, t.order", + 'select * from Survey_tempReport t where t.assetId=? order by t.Survey_responseId, t.order', [ $self->getId() ] ); return $self->export( $filename, $content ); } @@ -1591,18 +1576,18 @@ Returns transposed results as a tabbed file. sub www_exportTransposedResults { my $self = shift; return $self->session->privilege->insufficient() - unless ( $self->session->user->isInGroup( $self->get("groupToViewReports") ) ); + if !$self->session->user->isInGroup( $self->get('groupToViewReports') ); $self->loadTempReportTable(); - my $filename = $self->session->url->escape( $self->get("title") . "_transposedResults.tab" ); + my $filename = $self->session->url->escape( $self->get('title') . '_transposedResults.tab' ); my $content = $self->session->db->quickTab( - "select r.userId, r.username, r.ipAddress, r.startDate, r.endDate, r.isComplete, t.* - from Survey_tempReport t - left join Survey_response r using(Survey_responseId) - where t.assetId=? - order by r.userId, r.Survey_responseId, t.order", + 'select r.userId, r.username, r.ipAddress, r.startDate, r.endDate, r.isComplete, t.*' + . ' from Survey_tempReport t' + . ' left join Survey_response r using(Survey_responseId)' + . ' where t.assetId=?' + . ' order by r.userId, r.Survey_responseId, t.order', [ $self->getId() ] ); return $self->export( $filename, $content ); } @@ -1633,8 +1618,8 @@ sub export { my $store = WebGUI::Storage->createTemp( $self->session ); my $tmpDir = $store->getPath(); my $filepath = $store->getPath($filename); - unless ( open TEMP, ">$filepath" ) { - return "Error - Could not open temporary file for writing. Please use the back button and try again"; + if ( !open TEMP, ">$filepath" ) { + return 'Error - Could not open temporary file for writing. Please use the back button and try again'; } print TEMP $content; close TEMP; @@ -1643,9 +1628,7 @@ sub export { $self->session->http->setRedirect($fileurl); return undef; -} ## end sub export - - +} #------------------------------------------------------------------- @@ -1658,16 +1641,16 @@ Loads the responses from the survey into the Survey_tempReport table, so that ot sub loadTempReportTable { my $self = shift; - my $refs = $self->session->db->buildArrayRefOfHashRefs( "select * from Survey_response where assetId = ?", + my $refs = $self->session->db->buildArrayRefOfHashRefs( 'select * from Survey_response where assetId = ?', [ $self->getId() ] ); - $self->session->db->write( "delete from Survey_tempReport where assetId = ?", [ $self->getId() ] ); - for my $ref (@$refs) { + $self->session->db->write( 'delete from Survey_tempReport where assetId = ?', [ $self->getId() ] ); + for my $ref (@{$refs}) { $self->responseJSON( undef, $ref->{Survey_responseId} ); my $count = 1; for my $q ( @{ $self->responseJSON->returnResponseForReporting() } ) { if ( @{ $q->{answers} } == 0 and $q->{comment} =~ /\w/ ) { $self->session->db->write( - "insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [ + 'insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [ $self->getId(), $ref->{Survey_responseId}, $count++, $q->{section}, $q->{sectionName}, $q->{question}, $q->{questionName}, $q->{questionComment}, undef, undef, undef, undef, @@ -1678,7 +1661,7 @@ sub loadTempReportTable { } for my $a ( @{ $q->{answers} } ) { $self->session->db->write( - "insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [ + 'insert into Survey_tempReport VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', [ $self->getId(), $ref->{Survey_responseId}, $count++, $q->{section}, $q->{sectionName}, $q->{question}, $q->{questionName}, $q->{questionComment}, $a->{id}, $a->{value}, $a->{comment}, $a->{time}, @@ -1686,8 +1669,8 @@ sub loadTempReportTable { ] ); } - } ## end for my $q ( @{ $self->responseJSON... - } ## end for my $ref (@$refs) + } + } return 1; } From 34340e56fb1d11bab74f5a0c507a6a4389e990c6 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Thu, 12 Feb 2009 01:07:22 +0000 Subject: [PATCH 69/90] Added Survey::surveyJSON_* convenience methods that auto-persist to the db --- lib/WebGUI/Asset/Wobject/Survey.pm | 143 ++++++++++++++++++++++------- t/Asset/Wobject/Survey.t | 26 +++--- 2 files changed, 122 insertions(+), 47 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 7bb065593..5fc41586d 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -258,13 +258,96 @@ sub duplicate { #------------------------------------------------------------------- -=head2 saveSurveyJSON ( ) +=head2 surveyJSON_update ( ) -Serializes the SurveyJSON instance and persists it to the database +Convenience method that delegates to L +and automatically calls L<"persistSurveyJSON"> afterwards. =cut -sub saveSurveyJSON { +sub surveyJSON_update { + my $self = shift; + my $ret = $self->surveyJSON->update(@_); + $self->persistSurveyJSON(); + return $ret; +} + +#------------------------------------------------------------------- + +=head2 surveyJSON_copy ( ) + +Convenience method that delegates to L +and automatically calls L<"persistSurveyJSON"> afterwards. + +=cut + +sub surveyJSON_copy { + my $self = shift; + my $ret =$self->surveyJSON->copy(@_); + $self->persistSurveyJSON(); + return $ret; +} + +#------------------------------------------------------------------- + +=head2 surveyJSON_remove ( ) + +Convenience method that delegates L +and automatically calls L<"persistSurveyJSON"> afterwards. + +=cut + +sub surveyJSON_remove { + my $self = shift; + my $ret = $self->surveyJSON->remove(@_); + $self->persistSurveyJSON(); + return $ret; +} + +#------------------------------------------------------------------- + +=head2 surveyJSON_newObject ( ) + +Convenience method that delegates L +and automatically calls L<"persistSurveyJSON"> afterwards. + +=cut + +sub surveyJSON_newObject { + my $self = shift; + my $ret = $self->surveyJSON->newObject(@_); + $self->persistSurveyJSON(); + return $ret; +} + +#------------------------------------------------------------------- + +=head2 recordResponses ( ) + +Convenience method that delegates to L +and automatically calls L<"persistSurveyJSON"> afterwards. + +=cut + +sub recordResponses { + my $self = shift; + my $ret = $self->responseJSON->recordResponses(@_); + $self->persistResponseJSON(); + return $ret; +} + +#------------------------------------------------------------------- + +=head2 persistSurveyJSON ( ) + +Serializes the SurveyJSON instance and persists it to the database. + +Calling this method is only required if you have directly accessed and modified +the L<"surveyJSON"> object. + +=cut + +sub persistSurveyJSON { my $self = shift; my $data = $self->surveyJSON->freeze(); @@ -281,7 +364,8 @@ Lazy-loading mutator for the L prope It is stored in the database as a serialized JSON-encoded string in the surveyJSON db field. -See also L<"saveSurveyJSON">. +If you access and change surveyJSON you will need to manually call L<"persistSurveyJSON"> +to have your changes persisted to the database. =head3 json (optional) @@ -316,7 +400,8 @@ Lazy-loading mutator for the L pro It is stored in the database as a serialized JSON-encoded string in the responseJSON db field. -See also L<"saveResponseJSON">. +If you access and change responseJSON you will need to manually call L<"persistResponseJSON"> +to have your changes persisted to the database. =head3 json (optional) @@ -355,7 +440,7 @@ sub responseJSON { =head2 www_editSurvey ( ) -Loads the initial edit survey page. All other edit actions are JSON calls from this page. +Loads the initial edit survey page. All other edit actions are ajax calls from this page. =cut @@ -386,24 +471,21 @@ sub www_submitObjectEdit { return $self->session->privilege->insufficient() if !$self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - my $responses = $self->session->form->paramsHashRef(); + my $params = $self->session->form->paramsHashRef(); - # Id is made up of: sectionIndex-questionIndex-answerIndex - my @address = split /-/, $responses->{id}; + # Id is made up of at most: sectionIndex-questionIndex-answerIndex + my @address = split /-/, $params->{id}; # See if any special actions were requested.. - if ( $responses->{delete} ) { + if ( $params->{delete} ) { return $self->deleteObject( \@address ); } - elsif ( $responses->{copy} ) { + elsif ( $params->{copy} ) { return $self->copyObject( \@address ); } - # Each object checks the address and then either updates or passes it to the correct child. New objects will have an index of -1. - my $message = $self->surveyJSON->update( \@address, $responses ); - - # Persist the changes - $self->saveSurveyJSON(); + # Update the addressed object + $self->surveyJSON_update( \@address, $params ); # Return the updated Survey structure return $self->www_loadSurvey( { address => \@address } ); @@ -466,7 +548,7 @@ sub www_jumpTo { # Set the nextResponse to be the index we're up to $self->session->log->debug("Found id: $id at index: $currentIndex in surveyOrder"); $self->responseJSON->nextResponse( $currentIndex ); - $self->saveResponseJSON(); + $self->persistResponseJSON(); # Manually persist ResponseJSON to the database return $self->www_takeSurvey; } @@ -497,9 +579,7 @@ sub copyObject { my ( $self, $address ) = @_; #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. - $address = $self->surveyJSON->copy($address); - - $self->saveSurveyJSON(); + $address = $self->surveyJSON_copy($address); #The parent address of the deleted object is returned. @@ -524,9 +604,7 @@ sub deleteObject { my ( $self, $address ) = @_; #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. - my $message = $self->surveyJSON->remove($address); - - $self->saveSurveyJSON(); + my $message = $self->surveyJSON_remove($address); #The parent address of the deleted object is returned. if ( @$address == 1 ) { @@ -588,7 +666,7 @@ sub www_dragDrop { my @bid = split /-/, $p->{before}->{id}; my $target = $self->surveyJSON->getObject( \@tid ); - $self->surveyJSON->remove( \@tid, 1 ); + $self->surveyJSON_remove( \@tid, 1 ); my $address = [0]; if ( @tid == 1 ) { @@ -635,7 +713,8 @@ sub www_dragDrop { } } - $self->saveSurveyJSON(); + # Manually persist SuveryJSON since we have directly modified it + $self->persistSurveyJSON(); return $self->www_loadSurvey( { address => $address } ); } @@ -981,9 +1060,7 @@ sub www_submitQuestions { my @goodResponses = keys %{$responses}; #load everything. - my $termInfo = $self->responseJSON->recordResponses( $responses ); - - $self->saveResponseJSON(); + my $termInfo = $self->recordResponses( $responses ); if ( $termInfo->[0] ) { $self->session->log->debug('Terminal, surveyEnd'); @@ -1228,13 +1305,13 @@ sub prepareShowSurveyTemplate { #------------------------------------------------------------------- -=head3 saveResponseJSON +=head3 persistResponseJSON Turns the response object into JSON and saves it to the DB. =cut -sub saveResponseJSON { +sub persistResponseJSON { my $self = shift; my $data = $self->responseJSON->freeze(); $self->session->db->write( 'update Survey_response set responseJSON = ? where Survey_responseId = ?', [ $data, $self->responseId ] ); @@ -1351,9 +1428,11 @@ sub responseId { } ); - # Store the newly created responseId and then persist ResponseJSON + # Store the newly created responseId $self->{responseId} = $responseId; - $self->saveResponseJSON(); + + # Manually persist ResponseJSON since we have changed $self->responseId + $self->persistResponseJSON(); } else { $self->session->log->debug("haveTaken ($haveTaken) >= allowedTakes ($allowedTakes)"); diff --git a/t/Asset/Wobject/Survey.t b/t/Asset/Wobject/Survey.t index dff69f175..9d8a1ea36 100644 --- a/t/Asset/Wobject/Survey.t +++ b/t/Asset/Wobject/Survey.t @@ -38,31 +38,27 @@ $survey = $import_node->addChild( { className => 'WebGUI::Asset::Wobject::Survey isa_ok($survey, 'WebGUI::Asset::Wobject::Survey'); # Load bare-bones survey, containing a single section (S0) -$survey->surveyJSON->update([0], { variable => 'S0' }); +$survey->surveyJSON_update([0], { variable => 'S0' }); # Add 2 questions to S0 -$survey->surveyJSON->newObject([0]); # S0Q0 -$survey->surveyJSON->update([0,0], { variable => 'S0Q0' }); -$survey->surveyJSON->newObject([0]); # S0Q1 -$survey->surveyJSON->update([0,1], { variable => 'S0Q1' }); +$survey->surveyJSON_newObject([0]); # S0Q0 +$survey->surveyJSON_update([0,0], { variable => 'S0Q0' }); +$survey->surveyJSON_newObject([0]); # S0Q1 +$survey->surveyJSON_update([0,1], { variable => 'S0Q1' }); # Add a new section (S1) -$survey->surveyJSON->newObject([]); # S1 -$survey->surveyJSON->update([1], { variable => 'S1' }); +$survey->surveyJSON_newObject([]); # S1 +$survey->surveyJSON_update([1], { variable => 'S1' }); # Add 2 questions to S1 -$survey->surveyJSON->newObject([1]); # S1Q0 -$survey->surveyJSON->update([1,0], { variable => 'S1Q0' }); -$survey->surveyJSON->newObject([1]); # S1Q1 -$survey->surveyJSON->update([1,1], { variable => 'S1Q1' }); - -# Persist to db -$survey->saveSurveyJSON(); +$survey->surveyJSON_newObject([1]); # S1Q0 +$survey->surveyJSON_update([1,0], { variable => 'S1Q0' }); +$survey->surveyJSON_newObject([1]); # S1Q1 +$survey->surveyJSON_update([1,1], { variable => 'S1Q1' }); # Now start a response as admin user $session->user( { userId =>3 } ); $survey->responseIdCookies(0); -#$survey->responseId( { noCookie => 1 }); # triggers loadBothJSON() #for my $address (@{ $survey->responseJSON->surveyOrder }) { # diag (Dumper $address); From a350398f412e2bd1ff3b2fab4acc5ec16fac1484 Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Fri, 13 Feb 2009 18:28:52 +0000 Subject: [PATCH 70/90] Logic is corrected, but the JS should probably be rewritten to be more like the WG PageLayout drag and drop so that it isn't so finicky. --- lib/WebGUI/Asset/Wobject/Survey.pm | 14 ++++++++++---- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 5fc41586d..a8b4ac4e8 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -673,6 +673,10 @@ sub www_dragDrop { #sections can only be inserted after another section so chop off the question and answer portion of $#bid = 0; $bid[0] = -1 if ( !defined $bid[0] ); + + #If target is being moved down, then before has just moved up do to the target being deleted + $bid[0]-- if($tid[0] < $bid[0]); + $self->surveyJSON->insertObject( $target, [ $bid[0] ] ); } elsif ( @tid == 2 ) { #questions can be moved to any section, but a pushed to the end of a new section. @@ -686,28 +690,30 @@ sub www_dragDrop { $bid[1] = $tid[1]; } if ( $bid[0] == $tid[0] ) { - #moved to top of current section $bid[1] = -1; } else { - #else move to the end of the selected section $bid[1] = $#{ $self->surveyJSON->questions( [ $bid[0] ] ) }; } } ## end elsif ( @bid == 1 ) + else{ #Moved within the same section + $bid[1]-- if($tid[1] < $bid[1]); + } $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1] ] ); } ## end elsif ( @tid == 2 ) elsif ( @tid == 3 ) { #answers can only be rearranged in the same question - if ( @bid == 2 and $bid[1] == $tid[1] ) { + if ( @bid == 2 and $bid[1] == $tid[1] ) {#moved to the top of the question $bid[2] = -1; $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } elsif ( @bid == 3 ) { + #If target is being moved down, then before has just moved up do to the target being deleted + $bid[2]-- if($tid[2] < $bid[2]); $self->surveyJSON->insertObject( $target, [ $bid[0], $bid[1], $bid[2] ] ); } else { - #else put it back where it was $self->surveyJSON->insertObject( $target, \@tid ); } diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 92aff4a41..3db23b48a 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -655,12 +655,11 @@ sub insertObject { # Figure out what to do by counting the number of elements in the $address array ref my $count = @{$address}; - return if !$count; # Use splice to rearrange the relevant array of objects.. if ( $count == 1 ) { - splice @{ $self->sections($address) }, sIndex($address) + 1, 0, $object; + splice @{ $self->sections($address) }, sIndex($address) +1, 0, $object; } elsif ( $count == 2 ) { splice @{ $self->questions($address) }, qIndex($address) + 1, 0, $object; From 7958aefe643e5e63128ba89885f3315add464e8c Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 16 Feb 2009 00:13:19 +0000 Subject: [PATCH 71/90] Survey documentation improvements --- lib/WebGUI/Asset/Wobject/Survey.pm | 143 +++++++++++++++-------------- 1 file changed, 73 insertions(+), 70 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index a8b4ac4e8..1c5586f8b 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -338,26 +338,6 @@ sub recordResponses { #------------------------------------------------------------------- -=head2 persistSurveyJSON ( ) - -Serializes the SurveyJSON instance and persists it to the database. - -Calling this method is only required if you have directly accessed and modified -the L<"surveyJSON"> object. - -=cut - -sub persistSurveyJSON { - my $self = shift; - - my $data = $self->surveyJSON->freeze(); - $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); - - return; -} - -#------------------------------------------------------------------- - =head2 surveyJSON ( [json] ) Lazy-loading mutator for the L property. @@ -578,11 +558,11 @@ See L sub copyObject { my ( $self, $address ) = @_; - #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. + # Each object checks the ref and then either updates or passes it to the correct child. + # New objects will have an index of -1. $address = $self->surveyJSON_copy($address); - #The parent address of the deleted object is returned. - + # The parent address of the deleted object is returned. return $self->www_loadSurvey( { address => $address } ); } @@ -603,25 +583,26 @@ See L sub deleteObject { my ( $self, $address ) = @_; - #each object checks the ref and then either updates or passes it to the correct child. New objects will have an index of -1. + # Each object checks the ref and then either updates or passes it to the correct child. + # New objects will have an index of -1. my $message = $self->surveyJSON_remove($address); - #The parent address of the deleted object is returned. - if ( @$address == 1 ) { - $$address[0] = 0; + # The parent address of the deleted object is returned. + if ( @{$address} == 1 ) { + $address->[0] = 0; } else { - pop( @{$address} ); # unless @$address == 1 and $$address[0] == 0; + pop @{$address}; } return $self->www_loadSurvey( { address => $address, message => $message } ); -} ## end sub deleteObject +} #------------------------------------------------------------------- =head2 www_newObject() -Creates a new object from a POST param containing the new objects id concat'd on hyphens. +Creates a new object from a POST param containing the new objects id concatenated on hyphens. =cut @@ -637,20 +618,20 @@ sub www_newObject { my @inAddress = split /-/, $ids; - #Don't save after this as the new object should not stay in the survey + # Don't save after this as the new object should not stay in the survey my $address = $self->surveyJSON->newObject( \@inAddress ); - #The new temp object has an address of NEW, which means it is not a real final address. - + # The new temp object has an address of NEW, which means it is not a real final address. return $self->www_loadSurvey( { address => $address, message => undef } ); -} ## end sub www_newObject +} #------------------------------------------------------------------- =head2 www_dragDrop -Takes two ids from a form POST. The "target" is the object being moved, the "before" is the object directly preceding the "target". +Takes two ids from a form POST. +The "target" is the object being moved, the "before" is the object directly preceding the "target". =cut @@ -727,9 +708,10 @@ sub www_dragDrop { #------------------------------------------------------------------- -=head2 www_loadSurvey([options]) +=head2 www_loadSurvey( [options] ) -For loading the survey during editing. Returns the survey meta list and the html data for editing a particular survey object. +For loading the survey during editing. +Returns the survey meta list and the html data for editing a particular survey object. =head3 options @@ -903,9 +885,11 @@ sub view { my $var = $self->getMenuVars; my ( $code, $overTakeLimit ) = $self->getResponseInfoForView(); - $var->{'lastResponseCompleted'} = $code; - $var->{'lastResponseTimedOut'} = $code > 1 ? 1 : 0; - $var->{'maxResponsesSubmitted'} = $overTakeLimit; + + $var->{lastResponseCompleted} = $code; + $var->{lastResponseTimedOut} = $code > 1 ? 1 : 0; + $var->{maxResponsesSubmitted} = $overTakeLimit; + my $out = $self->processTemplate( $var, undef, $self->{_viewTemplate} ); return $out; @@ -921,20 +905,19 @@ Returns the top menu template variables as a hashref. sub getMenuVars { my $self = shift; - my %var; - - $var{'edit_survey_url'} = $self->getUrl('func=editSurvey'); - $var{'take_survey_url'} = $self->getUrl('func=takeSurvey'); - $var{'delete_responses_url'} = $self->getUrl('func=deleteResponses'); - $var{'view_simple_results_url'} = $self->getUrl('func=exportSimpleResults'); - $var{'view_transposed_results_url'} = $self->getUrl('func=exportTransposedResults'); - $var{'view_statistical_overview_url'} = $self->getUrl('func=viewStatisticalOverview'); - $var{'view_grade_book_url'} = $self->getUrl('func=viewGradeBook'); - $var{'user_canTakeSurvey'} = $self->session->user->isInGroup( $self->get('groupToTakeSurvey') ); - $var{'user_canViewReports'} = $self->session->user->isInGroup( $self->get('groupToViewReports') ); - $var{'user_canEditSurvey'} = $self->session->user->isInGroup( $self->get('groupToEditSurvey') ); - return \%var; + return { + edit_survey_url => $self->getUrl('func=>editSurvey'), + take_survey_url => $self->getUrl('func=>takeSurvey'), + delete_responses_url => $self->getUrl('func=>deleteResponses'), + view_simple_results_url => $self->getUrl('func=>exportSimpleResults'), + view_transposed_results_url => $self->getUrl('func=>exportTransposedResults'), + view_statistical_overview_url => $self->getUrl('func=>viewStatisticalOverview'), + view_grade_book_url => $self->getUrl('func=>viewGradeBook'), + user_canTakeSurvey => $self->session->user->isInGroup( $self->get('groupToTakeSurvey') ), + user_canViewReports => $self->session->user->isInGroup( $self->get('groupToViewReports') ), + user_canEditSurvey => $self->session->user->isInGroup( $self->get('groupToEditSurvey') ), + }; } #------------------------------------------------------------------- @@ -1077,20 +1060,20 @@ sub www_submitQuestions { # my $files = 0; # -# # for my $id(@$orderOf){ -# #if a file upload, write to disk -# # my $path; -# # if($id->{'questionType'} eq 'File Upload'){ -# # $files = 1; -# # my $storage = WebGUI::Storage->create($self->session); -# # my $filename = $storage->addFileFromFormPost( $id->{'Survey_answerId'} ); -# # $path = $storage->getPath($filename); -# # } -# #$self->session->errorHandler->error("Inserting a response ".$id->{'Survey_answerId'}." $responseId, $path, ".$$responses{$id->{'Survey_answerId'}}); -# # $self->session->db->write("insert into Survey_questionResponse -# # select ?, Survey_sectionId, Survey_questionId, Survey_answerId, ?, ?, ?, now(), ?, ? from Survey_answer where Survey_answerId = ?", -# # [$self->getId(), $responseId, $$responses{ $id->{'Survey_answerId'} }, '', $path, ++$lastOrder, $id->{'Survey_answerId'}]); -# # } +# for my $id(@$orderOf){ +# if a file upload, write to disk +# my $path; +# if($id->{'questionType'} eq 'File Upload'){ +# $files = 1; +# my $storage = WebGUI::Storage->create($self->session); +# my $filename = $storage->addFileFromFormPost( $id->{'Survey_answerId'} ); +# $path = $storage->getPath($filename); +# } +# $self->session->errorHandler->error("Inserting a response ".$id->{'Survey_answerId'}." $responseId, $path, ".$$responses{$id->{'Survey_answerId'}}); +# $self->session->db->write("insert into Survey_questionResponse +# select ?, Survey_sectionId, Survey_questionId, Survey_answerId, ?, ?, ?, now(), ?, ? from Survey_answer where Survey_answerId = ?", +# [$self->getId(), $responseId, $$responses{ $id->{'Survey_answerId'} }, '', $path, ++$lastOrder, $id->{'Survey_answerId'}]); +# } # if ($files) { # ##special case, need to check for more questions in section, if not, more current up one # my $lastA = $self->getLastAnswerInfo($responseId); @@ -1105,6 +1088,7 @@ sub www_submitQuestions { # return; # } # return $self->www_loadQuestions($responseId); + } #------------------------------------------------------------------- @@ -1311,6 +1295,26 @@ sub prepareShowSurveyTemplate { #------------------------------------------------------------------- +=head2 persistSurveyJSON ( ) + +Serializes the SurveyJSON instance and persists it to the database. + +Calling this method is only required if you have directly accessed and modified +the L<"surveyJSON"> object. + +=cut + +sub persistSurveyJSON { + my $self = shift; + + my $data = $self->surveyJSON->freeze(); + $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); + + return; +} + +#------------------------------------------------------------------- + =head3 persistResponseJSON Turns the response object into JSON and saves it to the DB. @@ -1466,7 +1470,6 @@ sub canTakeSurvey { return 0; } - #Does user have too many finished survey responses my $maxTakes = $self->getValue('maxResponsesPerUser'); my $ip = $self->session->env->getIp; my $id = $self->session->user->userId(); @@ -1613,7 +1616,7 @@ sub www_viewStatisticalOverview { }; } } - push@questionloop,{ + push @questionloop, { question => $question->{text}, question_id => "${sectionIndex}_$questionIndex", question_isMultipleChoice => ($questionType eq 'Multiple Choice'), @@ -1699,7 +1702,7 @@ sub export { $filename =~ s/[^\w\d\.]/_/g; my $content = shift; - #Create a temporary directory to store files if it doesn't already exist + # Create a temporary directory to store files if it doesn't already exist my $store = WebGUI::Storage->createTemp( $self->session ); my $tmpDir = $store->getPath(); my $filepath = $store->getPath($filename); From bc9c66c2fdd8009db2b509b6d3cd516e915eb133 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 16 Feb 2009 00:13:36 +0000 Subject: [PATCH 72/90] Fixed template var regression in Survey.pm --- lib/WebGUI/Asset/Wobject/Survey.pm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 1c5586f8b..e26c33ee2 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -907,13 +907,13 @@ sub getMenuVars { my $self = shift; return { - edit_survey_url => $self->getUrl('func=>editSurvey'), - take_survey_url => $self->getUrl('func=>takeSurvey'), - delete_responses_url => $self->getUrl('func=>deleteResponses'), - view_simple_results_url => $self->getUrl('func=>exportSimpleResults'), - view_transposed_results_url => $self->getUrl('func=>exportTransposedResults'), - view_statistical_overview_url => $self->getUrl('func=>viewStatisticalOverview'), - view_grade_book_url => $self->getUrl('func=>viewGradeBook'), + edit_survey_url => $self->getUrl('func=editSurvey'), + take_survey_url => $self->getUrl('func=takeSurvey'), + delete_responses_url => $self->getUrl('func=deleteResponses'), + view_simple_results_url => $self->getUrl('func=exportSimpleResults'), + view_transposed_results_url => $self->getUrl('func=exportTransposedResults'), + view_statistical_overview_url => $self->getUrl('func=viewStatisticalOverview'), + view_grade_book_url => $self->getUrl('func=viewGradeBook'), user_canTakeSurvey => $self->session->user->isInGroup( $self->get('groupToTakeSurvey') ), user_canViewReports => $self->session->user->isInGroup( $self->get('groupToViewReports') ), user_canEditSurvey => $self->session->user->isInGroup( $self->get('groupToEditSurvey') ), From 50091e8e3a0575934799d2049d1714514bf4c022 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 16 Feb 2009 00:13:58 +0000 Subject: [PATCH 73/90] Started working on Survey Multiple Choice bundle management. --- .../Asset/Wobject/Survey/ResponseJSON.pm | 59 +---- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 209 ++++++++---------- t/Asset/Wobject/Survey/ResponseJSON.t | 38 +++- 3 files changed, 139 insertions(+), 167 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index d5a305c61..a5f8a2f2d 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -461,56 +461,12 @@ sub recordResponses { my $self = shift; my ($responses) = validate_pos( @_, { type => HASHREF } ); - my %mcTypes = ( - 'Agree/Disagree' => 1, - Certainty => 1, - Concern => 1, - Confidence => 1, - Education => 1, - Effectiveness => 1, - Gender => 1, - Ideology => 1, - Importance => 1, - Likelihood => 1, - Party => 1, - 'Multiple Choice' => 1, - 'Oppose/Support' => 1, - Race => 1, - Risk => 1, - Satisfaction => 1, - Scale => 1, - Security => 1, - Threat => 1, - 'True/False' => 1, - 'Yes/No' => 1, - ); - my %sliderTypes = ( - 'Dual Slider - Range' => 1, - 'Multi Slider - Allocate' => 1, - Slider => 1, - ); - my %textTypes = ( - Currency => 1, - Email => 1, - 'Phone Number' => 1, - Text => 1, - 'Text Date' => 1, - 'TextArea' => 1, - ); - my %fileTypes = ( - 'File Upload' => 1, - ); - my %dateTypes = ( - Date => 1, - 'Date Range' => 1, - ); - my %hiddenTypes = ( - Hidden => 1, - ); + # Build a lookup table of non-multiple choice question types + my %knownTypes = map {$_ => 1} $self->survey->specialQuestionTypes; # We want to record responses against the "next" response section and questions, since these are # the items that have just been displayed to the user. - my $section = $self->nextResponseSection(); + my $section = $self->nextResponseSection(); my @questions = $self->nextQuestions(); # Handle terminal Section.. @@ -554,11 +510,12 @@ sub recordResponses { # Proceed if we're satisfied that response is valid.. if ( defined $answerValue && $answerValue =~ /\S/ ) { $aAnswered = 1; - if ( exists $mcTypes{ $question->{questionType} } ) { - $self->responses->{ $answer->{id} }->{value} = $answer->{recordedAnswer}; - } - else { + if ($knownTypes{$question->{questionType}}) { $self->responses->{ $answer->{id} }->{value} = $answerValue; + } else { + # Unknown type, must be a multi-choice bundle + # For Multi-choice, use recordedAnswer instead of answerValue + $self->responses->{ $answer->{id} }->{value} = $answer->{recordedAnswer}; } $self->responses->{ $answer->{id} }->{time} = time; $self->responses->{ $answer->{id} }->{comment} = $answerComment; diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 3db23b48a..97140d484 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -59,6 +59,69 @@ use Storable qw/dclone/; # The maximum value of questionsPerPage is currently hardcoded here my $MAX_QUESTIONS_PER_PAGE = 20; +my %MULTI_CHOICE_BUNDLES = ( + 'Agree/Disagree' => [ 'Strongly disagree', (q{}) x 5, 'Strongly agree' ], + Certainty => [ 'Not at all certain', (q{}) x 9, 'Extremely certain' ], + Concern => [ 'Not at all concerned', (q{}) x 9, 'Extremely concerned' ], + Confidence => [ 'Not at all confident', (q{}) x 9, 'Extremely confident' ], + Education => [ + 'Elementary or some high school', + 'High school/GED', + 'Some college/vocational school', + 'College graduate', + 'Some graduate work', + 'Master\'s degree', + 'Doctorate (of any type)', + 'Other degree (verbatim)', + ], + Effectiveness => [ 'Not at all effective', (q{}) x 9, 'Extremely effective' ], + Gender => [qw( Male Female )], + Ideology => [ + 'Strongly liberal', + 'Liberal', + 'Somewhat liberal', + 'Middle of the road', + 'Slightly conservative', + 'Conservative', + 'Strongly conservative' + ], + Importance => [ 'Not at all important', (q{}) x 9, 'Extremely important' ], + Likelihood => [ 'Not at all likely', (q{}) x 9, 'Extremely likely' ], + 'Oppose/Support' => [ 'Strongly oppose', (q{}) x 5, 'Strongly support' ], + Party => + [ 'Democratic party', 'Republican party (or GOP)', 'Independant party', 'Other party (verbatim)' ], + Race => + [ 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', 'Something else (verbatim)' ], + Risk => [ 'No risk', (q{}) x 9, 'Extreme risk' ], + Satisfaction => [ 'Not at all satisfied', (q{}) x 9, 'Extremely satisfied' ], + Security => [ 'Not at all secure', (q{}) x 9, 'Extremely secure' ], + Threat => [ 'No threat', (q{}) x 9, 'Extreme threat' ], + 'True/False' => [qw( True False )], + 'Yes/No' => [qw( Yes No )], + Scale => [q{}], + 'Multiple Choice' => [q{}], +); + +my @SPECIAL_QUESTION_TYPES = ( + 'Dual Slider - Range', + 'Multi Slider - Allocate', + 'Slider', + 'Currency', + 'Email', + 'Phone Number', + 'Text', + 'Text Date', + 'TextArea', + 'File Upload', + 'Date', + 'Date Range', + 'Hidden', +); + +sub specialQuestionTypes { + return @SPECIAL_QUESTION_TYPES; +} + =head2 new ( $session, json ) Object constructor. @@ -461,24 +524,12 @@ sub getQuestionEditVars { =head2 getValidQuestionTypes -A convenience method. Returns a list of question types. If you add a question -type to the Survey, you must handle it in the following places: here, updateQuestionAnswers, -recordResponses (ResponseJSON) and administersurvey.js +A convenience method. Returns a list of question types. =cut sub getValidQuestionTypes { - return ( - 'Agree/Disagree', 'Certainty', 'Concern', 'Confidence', - 'Currency', 'Date', 'Date Range', 'Dual Slider - Range', - 'Education', 'Effectiveness', 'Email', 'File Upload', - 'Gender', 'Hidden', 'Ideology', 'Importance', - 'Likelihood', 'Multi Slider - Allocate', 'Multiple Choice', 'Oppose/Support', - 'Party', 'Phone Number', 'Race', 'Risk', - 'Satisfaction', 'Scale', 'Security', 'Slider', - 'Text', 'TextArea', 'Text Date', 'Threat', - 'True/False', 'Yes/No' - ); + return sort (@SPECIAL_QUESTION_TYPES, keys %MULTI_CHOICE_BUNDLES); } =head2 getAnswerEditVars ( $address ) @@ -888,9 +939,6 @@ sub updateQuestionAnswers { # Add the default set of answers. The question type determines both the number # of answers added and the answer text to use. When updating answer text # first update $address_copy to point to the answer - - # TODO: Rather than being hard-coded, these question type/answer bundles should - # be loaded dynamically and customizable by the user (see also getValidQuestionTypes) if ( $type eq 'Date Range' or $type eq 'Multi Slider - Allocate' @@ -919,108 +967,41 @@ sub updateQuestionAnswers { $address_copy[2] = 0; $self->update( \@address_copy, { 'text', 'Email:' } ); } - elsif ( $type eq 'Education' ) { - my @ans = ( - 'Elementary or some high school', - 'High school/GED', - 'Some college/vocational school', - 'College graduate', - 'Some graduate work', - 'Master\'s degree', - 'Doctorate (of any type)', - 'Other degree (verbatim)', - ); - $self->addAnswersToQuestion( \@address_copy, \@ans, { 7, 1 } ); - } - elsif ( $type eq 'Party' ) { - my @ans - = ( 'Democratic party', 'Republican party (or GOP)', 'Independant party', 'Other party (verbatim)' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, { 3, 1 } ); - } - elsif ( $type eq 'Race' ) { - my @ans = ( 'American Indian', 'Asian', 'Black', 'Hispanic', 'White non-Hispanic', - 'Something else (verbatim)', ); - $self->addAnswersToQuestion( \@address_copy, \@ans, { 5, 1 } ); - } - elsif ( $type eq 'Ideology' ) { - my @ans = ( - 'Strongly liberal', - 'Liberal', - 'Somewhat liberal', - 'Middle of the road', - 'Slightly conservative', - 'Conservative', - 'Strongly conservative', - ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Security' ) { - my @ans = ( 'Not at all secure', (q{}) x 9, 'Extremely secure', ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Threat' ) { - my @ans = ( 'No threat', (q{}) x 9, 'Extreme threat', ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Risk' ) { - my @ans = ( 'No risk', (q{}) x 9, 'Extreme risk' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Concern' ) { - my @ans = ( 'Not at all concerned', (q{}) x 9, 'Extremely concerned' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Effectiveness' ) { - my @ans = ( 'Not at all effective', (q{}) x 9, 'Extremely effective' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Confidence' ) { - my @ans = ( 'Not at all confident', (q{}) x 9, 'Extremely confident' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Satisfaction' ) { - my @ans = ( 'Not at all satisfied', (q{}) x 9, 'Extremely satisfied' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Certainty' ) { - my @ans = ( 'Not at all certain', (q{}) x 9, 'Extremely certain' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Likelihood' ) { - my @ans = ( 'Not at all likely', (q{}) x 9, 'Extremely likely' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Importance' ) { - my @ans = ( 'Not at all important', (q{}) x 9, 'Extremely important' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Oppose/Support' ) { - my @ans = ( 'Strongly oppose', (q{}) x 5, 'Strongly support' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Agree/Disagree' ) { - my @ans = ( 'Strongly disagree', (q{}) x 5, 'Strongly agree' ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'True/False' ) { - my @ans = qw( True False ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Yes/No' ) { - my @ans = qw( Yes No ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - elsif ( $type eq 'Gender' ) { - my @ans = qw( Male Female ); - $self->addAnswersToQuestion( \@address_copy, \@ans, {} ); - } - else { + elsif ( my $answerBundle = $self->getMultiChoiceBundle($type) ) { + # We found a known multi-choice bundle. + + # Mark any answer containing the string "verbatim" as verbatim + my $verbatims = {}; + for my $answerIndex (0 .. $#$answerBundle) { + if ($answerBundle->[$answerIndex] =~ /\(verbatim\)/) { + $verbatims->{$answerIndex} = 1; + } + } + # Add the bundle of multi-choice answers, along with the verbatims hash + $self->addAnswersToQuestion( \@address_copy, $answerBundle, $verbatims ); + } else { + # Default action is to add a single, default answer to the question push @{ $question->{answers} }, $self->newAnswer(); } - + return; } +=head2 getMultiChoiceBundle + +Returns a list of answers for each multi-choice bundle. + +Currently these are hard-coded but soon they will live in the database. + +=cut + +sub getMultiChoiceBundle { + my $self = shift; + my ($type) = validate_pos( @_, { type => SCALAR | UNDEF } ); + + return $MULTI_CHOICE_BUNDLES{$type}; +} + =head2 addAnswersToQuestion ($address, $answers, $verbatims) Helper routine for updateQuestionAnswers. Adds an array of answers to a question. diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 1e25d7a2e..d278dd9d3 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -21,7 +21,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 78; +my $tests = 79; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -430,6 +430,7 @@ cmp_deeply( [ 1, 'question 1-0 terminal' ], 'recordResponses: question terminal overrides section terminal', ); + is($rJSON->lastResponse(), 4, 'lastResponse advanced to next page of questions'); is($rJSON->questionsAnswered, 1, 'questionsAnswered=1, answered one question'); @@ -442,7 +443,7 @@ cmp_deeply( '1-0-0' => { comment => 'Section 1, question 0, answer 0 comment', 'time' => num(time(), 3), - value => 1, + value => 1, # 'recordedAnswer' value used because question is multi-choice }, '1-1' => { comment => undef, @@ -451,6 +452,36 @@ cmp_deeply( 'recordResponses: recorded responses correctly, two questions, one answer, comments, values and time' ); + +# Repeat with non multi-choice question, to check that submitted answer value is used +# instead of recordedValue +$rJSON->survey->question([1,0])->{questionType} = 'Text'; +$rJSON->lastResponse(2); +$rJSON->questionsAnswered(-1 * $rJSON->questionsAnswered); +$rJSON->recordResponses({ + '1-0comment' => 'Section 1, question 0 comment', + '1-0-0' => 'First answer', + '1-0-0comment' => 'Section 1, question 0, answer 0 comment', +}); +cmp_deeply( + $rJSON->responses, + { + '1-0' => { + comment => 'Section 1, question 0 comment', + }, + '1-0-0' => { + comment => 'Section 1, question 0, answer 0 comment', + 'time' => num(time(), 3), + value => 'First answer', # submitted answer value used this time because non-mc + }, + '1-1' => { + comment => undef, + } + }, + 'recordResponses: recorded responses correctly, two questions, one answer, comments, values and time' +); +$rJSON->survey->question([1,0])->{questionType} = 'Multiple Choice'; # revert change + $rJSON->survey->question([1,0,0])->{terminal} = 1; $rJSON->survey->question([1,0,0])->{terminalUrl} = 'answer 1-0-0 terminal'; $rJSON->responses({}); @@ -480,6 +511,9 @@ cmp_deeply( 'recordResponses: if the answer is all whitespace, it is skipped over' ); is($rJSON->questionsAnswered, 0, 'question was all whitespace, not answered'); +#delete $rJSON->{_session}; +#delete $rJSON->survey->{_session}; +#diag(Dumper($rJSON)); } From 16c122a550ac419e1efcc5d436e21a16a54d865e Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 16 Feb 2009 01:21:47 +0000 Subject: [PATCH 74/90] Fixed regression in ResponseJSON's sectionId, questionId and answerId methods --- lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index a5f8a2f2d..ab56e6091 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -957,7 +957,10 @@ A Section Id is identical to a Section index. This method is only present for co sub sectionId { my $self = shift; - my ($sIndex) = validate_pos(@_, { type => SCALAR } ); + my ($sIndex) = validate_pos(@_, { type => SCALAR | UNDEF } ); + + return if !defined $sIndex; + return $sIndex; } @@ -971,7 +974,10 @@ The id is constructed by hyphenating the Section index and Question index. sub questionId { my $self = shift; - my ($sIndex, $qIndex) = validate_pos(@_, { type => SCALAR }, { type => SCALAR } ); + my ($sIndex, $qIndex) = validate_pos(@_, { type => SCALAR | UNDEF }, { type => SCALAR | UNDEF } ); + + return if !defined $sIndex || !defined $qIndex; + return "$sIndex-$qIndex"; } @@ -985,7 +991,10 @@ The id is constructed by hyphenating all three indices. sub answerId { my $self = shift; - my ($sIndex, $qIndex, $aIndex) = validate_pos(@_, { type => SCALAR }, { type => SCALAR }, { type => SCALAR } ); + my ($sIndex, $qIndex, $aIndex) = validate_pos(@_, { type => SCALAR | UNDEF }, { type => SCALAR | UNDEF }, { type => SCALAR | UNDEF } ); + + return if !defined $sIndex || !defined $qIndex || !defined $aIndex; + return "$sIndex-$qIndex-$aIndex"; } From c58a6ab2fb6e42a56cf304da8f577db099b7b89e Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Mon, 16 Feb 2009 01:22:04 +0000 Subject: [PATCH 75/90] Removed hard-coding of multi-choice bundles from administersurvey.js Refactored long, nested if-else --- www/extras/wobject/Survey/administersurvey.js | 216 ++++++++---------- 1 file changed, 98 insertions(+), 118 deletions(-) diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index c16533830..ea165c431 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -8,50 +8,29 @@ if (typeof Survey === "undefined") { var CLASS_INVALID = 'survey-invalid'; // For elements that fail input validation var CLASS_INVALID_MARKER = 'survey-invalid-marker'; // For default '*' invalid field marker - var multipleChoice = { - 'Multiple Choice': 1, - 'Gender': 1, - 'Yes/No': 1, - 'True/False': 1, - 'Ideology': 1, - 'Race': 1, - 'Party': 1, - 'Education': 1, - 'Scale': 1, - 'Agree/Disagree': 1, - 'Oppose/Support': 1, - 'Importance': 1, - 'Likelihood': 1, - 'Certainty': 1, - 'Satisfaction': 1, - 'Confidence': 1, - 'Effectiveness': 1, - 'Concern': 1, - 'Risk': 1, - 'Threat': 1, - 'Security': 1 - }; - var text = { + // All specially-handled question types are listed here + // (anything else is assumed to be a multi-choice bundle) + var TEXT_TYPES = { 'Text': 1, 'Email': 1, 'Phone Number': 1, 'Text Date': 1, 'Currency': 1, - 'TextArea': 1 + 'TextArea': 1 }; - var slider = { + var SLIDER_TYPES = { 'Slider': 1, 'Dual Slider - Range': 1, 'Multi Slider - Allocate': 1 }; - var dateType = { + var DATE_TYPES = { 'Date': 1, 'Date Range': 1 }; - var fileUpload = { + var UPLOAD_TYPES = { 'File Upload': 1 }; - var hidden = { + var HIDDEN_TYPES = { 'Hidden': 1 }; @@ -390,12 +369,12 @@ if (typeof Survey === "undefined") { addWidgets: function(qs){ hasFile = false; for (var i = 0; i < qs.length; i++) { - var q = qs[i]; - if (!q || !q.answers) { - // gracefully handle q with no answers - continue; - } - + var q = qs[i]; + if (!q || !q.answers) { + // gracefully handle q with no answers + continue; + } + var verts = ''; for (var x in q.answers) { if (YAHOO.lang.hasOwnProperty(q.answers, x)) { @@ -417,98 +396,99 @@ if (typeof Survey === "undefined") { } - if (multipleChoice[q.questionType]) { - var butts = []; - verb = 0; - for (var j = 0; j < q.answers.length; j++) { - var a = q.answers[j]; + if (DATE_TYPES[q.questionType]) { + for (var k = 0; k < q.answers.length; k++) { + var ans = q.answers[k]; if (toValidate[q.id]) { - toValidate[q.id].answers[a.id] = 1; + toValidate[q.id].answers[ans.id] = 1; } - var b = document.getElementById(a.id + 'button'); - /* - b = new YAHOO.widget.Button({ type: "checkbox", label: a.answerText, id: a.id+'button', name: a.id+'button', - value: a.id, - container: a.id+"container", checked: false }); - */ - // b.on("click", buttonChanged,[b,a.id,q.maxAnswers,butts,qs.length,a.id]); - // YAHOO.util.Event.addListener(a.id+'button', "click", buttonChanged,[b,a.id,q.maxAnswers,butts,qs.length,a.id]); - if (a.verbatim) { - verb = 1; - } - YAHOO.util.Event.addListener(a.id + 'button', "click", buttonChanged, [b, a.id, q.maxAnswers, butts, qs.length, a.id]); - b.hid = a.id; - butts.push(b); + var calid = ans.id + 'container'; + var c = new YAHOO.widget.Calendar(calid, { + title: 'Choose a date:', + close: true + }); + c.selectEvent.subscribe(selectCalendar, [c, ans.id], true); + c.render(); + c.hide(); + var btn = new YAHOO.widget.Button({ + label: "Select Date", + id: "pushbutton" + ans.id, + container: ans.id + 'button' + }); + btn.on("click", showCalendar, [c]); } + continue; } - else - if (dateType[q.questionType]) { - for (var k = 0; k < q.answers.length; k++) { - var ans = q.answers[k]; - if (toValidate[q.id]) { - toValidate[q.id].answers[ans.id] = 1; + + if (SLIDER_TYPES[q.questionType]) { + //First run through and put up the span placeholders and find the max value for an answer, to know how big the allocation points will be. + var max = 0; + if (q.questionType === 'Dual Slider - Range') { + handleDualSliders(q); + } + else { + for (var s in q.answers) { + if (YAHOO.lang.hasOwnProperty(q.answers, s)) { + var a1 = q.answers[s]; + YAHOO.util.Event.addListener(a1.id, "blur", sliderTextSet); + if (a1.max - a1.min > max) { + max = a1.max - a1.min; + } } - var calid = ans.id + 'container'; - var c = new YAHOO.widget.Calendar(calid, { - title: 'Choose a date:', - close: true - }); - c.selectEvent.subscribe(selectCalendar, [c, ans.id], true); - c.render(); - c.hide(); - var btn = new YAHOO.widget.Button({ - label: "Select Date", - id: "pushbutton" + ans.id, - container: ans.id + 'button' - }); - btn.on("click", showCalendar, [c]); } } + if (q.questionType === 'Multi Slider - Allocate') { + //sliderManagers[sliderManagers.length] = new this.sliderManager(q,max); + for (var x1 = 0; x1 < q.answers.length; x1++) { + if (toValidate[q.id]) { + toValidate[q.id].total = q.answers[x1].max; + toValidate[q.id].answers[q.answers[x1].id] = 1; + } + } + sliderManager(q, max); + } else - if (slider[q.questionType]) { - //First run through and put up the span placeholders and find the max value for an answer, to know how big the allocation points will be. - var max = 0; - if (q.questionType === 'Dual Slider - Range') { - handleDualSliders(q); - } - else { - for (var s in q.answers) { - if (YAHOO.lang.hasOwnProperty(q.answers, s)) { - var a1 = q.answers[s]; - YAHOO.util.Event.addListener(a1.id, "blur", sliderTextSet); - if (a1.max - a1.min > max) { - max = a1.max - a1.min; - } - } - } - } - if (q.questionType === 'Multi Slider - Allocate') { - //sliderManagers[sliderManagers.length] = new this.sliderManager(q,max); - for (var x1 = 0; x1 < q.answers.length; x1++) { - if (toValidate[q.id]) { - toValidate[q.id].total = q.answers[x1].max; - toValidate[q.id].answers[q.answers[x1].id] = 1; - } - } - sliderManager(q, max); - } - else - if (q.questionType === 'Slider') { - handleSliders(q); - } + if (q.questionType === 'Slider') { + handleSliders(q); } - - else - if (fileUpload[q.questionType]) { - hasFile = true; - } - - else - if (text[q.questionType]) { - if (toValidate[q.id]) { - toValidate[q.id].answers[q.answers[x].id] = 1; - } - } + continue; + } + + if (UPLOAD_TYPES[q.questionType]) { + hasFile = true; + continue; + } + + if (TEXT_TYPES[q.questionType]) { + if (toValidate[q.id]) { + toValidate[q.id].answers[q.answers[x].id] = 1; + } + continue; + } + + // Must be a multi-choice bundle + var butts = []; + verb = 0; + for (var j = 0; j < q.answers.length; j++) { + var a = q.answers[j]; + if (toValidate[q.id]) { + toValidate[q.id].answers[a.id] = 1; + } + var b = document.getElementById(a.id + 'button'); + /* + b = new YAHOO.widget.Button({ type: "checkbox", label: a.answerText, id: a.id+'button', name: a.id+'button', + value: a.id, + container: a.id+"container", checked: false }); + */ + // b.on("click", buttonChanged,[b,a.id,q.maxAnswers,butts,qs.length,a.id]); + // YAHOO.util.Event.addListener(a.id+'button', "click", buttonChanged,[b,a.id,q.maxAnswers,butts,qs.length,a.id]); + if (a.verbatim) { + verb = 1; + } + YAHOO.util.Event.addListener(a.id + 'button', "click", buttonChanged, [b, a.id, q.maxAnswers, butts, qs.length, a.id]); + b.hid = a.id; + butts.push(b); + } } YAHOO.util.Event.addListener("submitbutton", "click", formsubmit); } From 9fe2b5328e96231a27df97834148443607411ebc Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Mon, 16 Feb 2009 23:49:16 +0000 Subject: [PATCH 76/90] Added workflow (and email template to WebGUI/) to delete expired surveys and notify users via email. --- .../ExpireIncompleteSurveyResponses.pm | 152 ++++++++++++++++++ ...ctivity_ExpireIncompleteSurveyResponses.pm | 67 ++++++++ root_import_prop-style.wgpkg | Bin 0 -> 5270 bytes 3 files changed, 219 insertions(+) create mode 100644 lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm create mode 100644 lib/WebGUI/i18n/English/Workflow_Activity_ExpireIncompleteSurveyResponses.pm create mode 100644 root_import_prop-style.wgpkg diff --git a/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm new file mode 100644 index 000000000..caea2c54d --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm @@ -0,0 +1,152 @@ +package WebGUI::Workflow::Activity::ExpireIncompleteSurveyResponses; + + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2008 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Workflow::Activity'; +use WebGUI::Asset; +use WebGUI::DateTime; +use DateTime::Duration; + +=head1 NAME + +Package WebGUI::Workflow::Activity::ExpireIncompleteSurveyResponses + +=head1 DESCRIPTION + +This activity deletes the survey responses for which the allowed time has expired and emails the survey user. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::defintion() for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, "Workflow_Activity_ExpireIncompleteSurveyResponses"); + push(@{$definition}, { + name => $i18n->get("name"), + properties => { + deleteExpired=>{ + fieldType=>"yesNo", + defaultValue=>0, + label=>$i18n->get("Delete expired survey responses"), + hoverHelp=>$i18n->get("delete expired") + }, + emailUsers=>{ + fieldType=>"yesNo", + defaultValue=>0, + label=>$i18n->get("Email users that responses were deleted"), + hoverHelp=>$i18n->get("email users") + }, + emailTemplateId => { + fieldType => "template", + defaultValue => 'ExpireIncResptmpl00001', + namespace => "ExpireIncompleteSurveyResponses", + label => $i18n->get('Email template sent to user'), + hoverHelp => $i18n->get('email template'), + }, + from => { + fieldType=>"text", + label=>$i18n->get("from"), + defaultValue=>$session->setting->get("companyEmail"), + hoverHelp=>$i18n->get("from mouse over"), + }, + subject => { + fieldType=>"text", + label=>$i18n->get("subject", 'WebGUI'), + defaultValue=>"Expired Survey", + hoverHelp=>$i18n->get("subject mouse over"), + }, + } + }); + return $class->SUPER::definition($session,$definition); +} + + +#------------------------------------------------------------------- + +=head2 execute ( [ object ] ) + +Finds all the expired Survey Responses on the system. If delete is selected, they are removed. Then if +email is selected, the users are emailed the template. + +=cut + +sub execute { + my $self = shift; + my $session = $self->session; + + my $sql = "select r.Survey_responseId, r.username, r.userId, upd.email,upd.firstName,upd.lastName, r.startDate, s.timeLimit, ad.title, ad.url + from Survey s, Survey_response r, assetData ad, userProfileData upd + where r.isComplete = 0 and s.timeLimit > 0 and (unix_timestamp() - r.startDate) > (s.timeLimit * 1) + and r.assetId = s.assetId and s.revisionDate = (select max(revisionDate) from Survey where assetId = s.assetId) + and ad.assetId = s.assetId and ad.revisionDate = s.revisionDate and upd.userId = r.userId"; + my $refs = $self->session->db->buildArrayRefOfHashRefs($sql); + for my $ref (@{$refs}) { + if($self->get("deleteExpired") == 1){ + $self->session->db->write("delete from Survey_response where Survey_responseId = ?",[$ref->{Survey_responseId}]); + }else{#else sent to expired but not deleted + $self->session->db->write("update Survey_response set isComplete = 99 where Survey_responseId = ?",[$ref->{Survey_responseId}]); + } + if($self->get("emailUsers") == 1 && $ref->{email} =~ /\@/){ + + my $var = { + to => $ref->{email}, + from => $self->get("from"), + firstName => $ref->{firstName}, + lastName => $ref->{lastName}, + surveyTitle => $ref->{title}, + surveyUrl => $ref->{url}, + responseId => $ref->{Survey_responseId}, + deleted => $self->get("deleteExpired"), + companyName => $self->session->setting->get("companyName"), + }; + my $template = WebGUI::Asset->newByDynamicClass($self->session,$self->get('emailTemplateId')); + my $message = $template->processTemplate($var, $self->get("emailTemplateId")); + WebGUI::Macro::process($self->session,\$message); + my $mail = WebGUI::Mail::Send->create($self->session,{ + to => $ref->{email}, + subject => $self->get("subject"), + from => $self->get('from'), + }); + $mail->addHtml($message); + $mail->addFooter; + $mail->queue; + } + } + return $self->COMPLETE; +} + +1; + + diff --git a/lib/WebGUI/i18n/English/Workflow_Activity_ExpireIncompleteSurveyResponses.pm b/lib/WebGUI/i18n/English/Workflow_Activity_ExpireIncompleteSurveyResponses.pm new file mode 100644 index 000000000..fc3bb8e85 --- /dev/null +++ b/lib/WebGUI/i18n/English/Workflow_Activity_ExpireIncompleteSurveyResponses.pm @@ -0,0 +1,67 @@ +package WebGUI::i18n::English::Workflow_Activity_ExpireIncompleteSurveyResponses; +use strict; + +our $I18N = { + 'name' => { + message => q|ExpireIncompleteSurveyResponses|, + lastUpdated => 0, + }, + 'Delete expired survey responses' => { + message => q|Delete expired survey responses|, + context => q|the hover help for the delete responses field|, + lastUpdated => 0, + }, + 'delete expired' => { + message => q|When ran, every survey response which is expired will be completely removed from the database.|, + context => q|the hover help for the delete responses field|, + lastUpdated => 0, + }, + 'Email users that responses were deleted' => { + message => q|Email users that responses were deleted|, + context => q|the hover help for the email users field|, + lastUpdated => 0, + }, + 'email users' => { + message => q|When a survey response is deleted, should the user be informed of this via email?|, + context => q|the hover help for the email users field|, + lastUpdated => 0, + }, + 'email template' => { + message => q|When an email is sent updating the user that their response has been deleted, this is the text that is sent to them.|, + context => q|the hover help for the email template field|, + lastUpdated => 0, + }, + 'from' => { + message => q|Email from field|, + context => q||, + lastUpdated => 0, + }, + 'from mouse over' => { + message => q|This is the from field that will show up in the sent email.|, + context => q||, + lastUpdated => 0, + }, + 'subject' => { + message => q|Email subject field|, + context => q||, + lastUpdated => 0, + }, + 'subject mouse over' => { + message => q|This is the subject field that will show up in the sent email.|, + context => q||, + lastUpdated => 0, + }, + 'Email template sent to user' => { + message => q|The template for the email|, + context => q||, + lastUpdated => 0, + }, + 'email template' => { + message => q|This is the email template that will be sent to the user|, + context => q||, + lastUpdated => 0, + }, + +}; + +1; diff --git a/root_import_prop-style.wgpkg b/root_import_prop-style.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..fea9f43c0194729cbcb7643515a7da1204bed255 GIT binary patch literal 5270 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 literal 0 HcmV?d00001 From d1c3aa1be13e27ef6b857f9da5bc1597690df4ef Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Mon, 16 Feb 2009 23:55:15 +0000 Subject: [PATCH 77/90] fixed typo in template --- ..._import_expireincompletesurveyresponses.wgpkg | Bin 0 -> 1138 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 root_import_expireincompletesurveyresponses.wgpkg diff --git a/root_import_expireincompletesurveyresponses.wgpkg b/root_import_expireincompletesurveyresponses.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..6ba373af7df93d78b020db8c84b89cc485be5033 GIT binary patch literal 1138 zcmV-&1daP2iwFP!000001MOJP}nthV=~b#*v*aX5AVk6hYo&wL&PdtW#u zTlHRQ51LIAcV$iG!6N*PP%b`>zaIo%&G|=Ae5v^FZ56^7Q}*{O8HG9*l%X-yowoDG zpRe_+l+hHJz`)g8Ev6)6%+mzr11u6PUia!+Uadt8kbwAIu6ERYvydPz&K^?ANSi}F7gBhAib7>SOCy_C**hC7^;uJ(uc_e7#7zo zIcId*Lkviv*pi8XM+~O|Q+=N0pr1?-89$pbn7|?->)QOI7G*q-WNPsbXj*`B1H<30 zJMH$d3S_%|Lx*1=k`L!JQD9g2t9}^bG?lAf!*8}4EjO@05mk!I)Rl4VRB`R= zxLz&Cb%F6G3`Mqv#w5ST=?5yL$q1Er)Ger{Kr&NQ8(UPRlj%&Zqg0mc%q|_B&wc#0 z0LKq4bC|!U(S3>h%D`xF4iCe_!-KG4ZD*j8j*`=|aGwKErYwr?%P3m8EPkA6Vaq@V zBw~3gdZi}I4}r#ubp5M2&YRt{uWzEBpxu6$+`hTIyEIBL@8?_^)+e$wa9N7SNjgD8 z5Hd7LQZz41LZ7un%I<2I7d;IPSG2+*BVpwV$2;|EC#Wby?NQ z%V}rzsb84!n{$9)81(B#c-d2Re>(-loVV&kcQg4k!i)L@ktsxS?W&SB?Q;_blC0$sMXNI!*RqL{9~p7YTUS$<6(l2Y ztr4(Yk|8IOq<7EFqko@%JUX_Ve3GB*Tz$gXN|f8}i$V8&`ORRyhPYsU3L}&yOElDu z)6X)RM{ciVG<-tm9db_DwBTY^6CtwhxcuKjLBgt*v-pXyl1gqwdRPuOzVPAq`xdag zg*)zTuRFHEdY<%blhU-@hDCE9QJNLMp*CP8+nR1WJv#(;2<#BpA+QR8@01i0)c_a( E0JZ5Y`Tzg` literal 0 HcmV?d00001 From ef548cbfee6162c2d6aec5eaf80f7d692ba9e927 Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Tue, 17 Feb 2009 14:20:35 +0000 Subject: [PATCH 78/90] Time multiplier was set wrong --- lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm index caea2c54d..25f5e2ea8 100644 --- a/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm +++ b/lib/WebGUI/Workflow/Activity/ExpireIncompleteSurveyResponses.pm @@ -108,7 +108,7 @@ sub execute { my $sql = "select r.Survey_responseId, r.username, r.userId, upd.email,upd.firstName,upd.lastName, r.startDate, s.timeLimit, ad.title, ad.url from Survey s, Survey_response r, assetData ad, userProfileData upd - where r.isComplete = 0 and s.timeLimit > 0 and (unix_timestamp() - r.startDate) > (s.timeLimit * 1) + where r.isComplete = 0 and s.timeLimit > 0 and (unix_timestamp() - r.startDate) > (s.timeLimit * 60) and r.assetId = s.assetId and s.revisionDate = (select max(revisionDate) from Survey where assetId = s.assetId) and ad.assetId = s.assetId and ad.revisionDate = s.revisionDate and upd.userId = r.userId"; my $refs = $self->session->db->buildArrayRefOfHashRefs($sql); From 6720ce5efbb700f69aa7786e1d4a5afed408c0b4 Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Tue, 17 Feb 2009 15:25:10 +0000 Subject: [PATCH 79/90] Drag and drop functionality vastly improved in usability. --- www/extras/wobject/Survey/dd.js | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/www/extras/wobject/Survey/dd.js b/www/extras/wobject/Survey/dd.js index 86559ebba..5c7feacb2 100644 --- a/www/extras/wobject/Survey/dd.js +++ b/www/extras/wobject/Survey/dd.js @@ -5,6 +5,8 @@ var Dom = YAHOO.util.Dom; var Event = YAHOO.util.Event; var DDM = YAHOO.util.DragDropMgr; +var currentDest; + Survey.DDList = function(id, sGroup, config) { Survey.DDList.superclass.constructor.call(this, id, sGroup, config); @@ -61,30 +63,10 @@ YAHOO.extend(Survey.DDList, YAHOO.util.DDProxy, { a.animate(); }, + onInvalidDrop: function(e, id) { + Survey.Data.dragDrop(this.getEl()); + }, onDragDrop: function(e, id) { - - // If there is one drop interaction, the li was dropped either on the list, - // or it was dropped on the current location of the source element. - if (DDM.interactionInfo.drop.length === 1) { - - // The position of the cursor at the time of the drop (YAHOO.util.Point) - var pt = DDM.interactionInfo.point; - - // The region occupied by the source element at the time of the drop - var region = DDM.interactionInfo.sourceRegion; - - // Check to see if we are over the source element's location. We will - // append to the bottom of the list once we are sure it was a drop in - // the negative space (the area of the list without any list items) - if (!region.intersect(pt)) { - var destEl = Dom.get(id); - var destDD = DDM.getDDById(id); - destEl.appendChild(this.getEl()); - destDD.isEmpty = false; - DDM.refreshCache(); - } - - } Survey.Data.dragDrop(this.getEl()); }, @@ -110,6 +92,8 @@ YAHOO.extend(Survey.DDList, YAHOO.util.DDProxy, { // We are only concerned with list items, we ignore the dragover // notifications for the list. if (destEl.nodeName.toLowerCase() == "li") { +currentDest = destEl; +console.log(destEl); var orig_p = srcEl.parentNode; var p = destEl.parentNode; From f275024f679c1dd8001ae2b88f622fc7ab72a602 Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Wed, 25 Feb 2009 18:53:14 +0000 Subject: [PATCH 80/90] Survey.pm now has surveyJSON as a normal definition property, which is also versioned. Survey Responses are not versioned, so updating a survey can cause errors with ongoing responses. --- lib/WebGUI/Asset/Wobject/Survey.pm | 88 ++++-------------------------- 1 file changed, 12 insertions(+), 76 deletions(-) diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index e26c33ee2..397cc5123 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -176,6 +176,12 @@ sub definition { defaultValue => 'PBtmpl0000000000000062', namespace => 'Survey/Gradebook', }, + surveyJSON => { + fieldType => 'text', + defaultValue => '', + autoGenerate => 0, + noFormPost => 1, + }, ); push @{$definition}, { @@ -192,72 +198,6 @@ sub definition { #------------------------------------------------------------------- -=head2 exportAssetData ( ) - -Override exportAssetData so that surveyJSON is included in package exports etc.. - -N.B. Currently ResponseJSON data is not exported. - -=cut - -sub exportAssetData { - my $self = shift; - - # Start off with the wobject data that Wobject knows about - my $hash = $self->SUPER::exportAssetData(); - - # Add in the SurveyJSON data.. - $hash->{properties}{surveyJSON} = $self->surveyJSON->freeze; - - return $hash; -} - -#------------------------------------------------------------------- - -=head2 importAssetData ( hashRef ) - -Override importAssetCollateralData so that surveyJSON gets imported from packages - -N.B. Currently ResponseJSON data is not imported. - -=cut - -sub importAssetCollateralData { - my ( $self, $data ) = @_; - - # Persist the SurveyJSON data to the database - my $surveyJSON = $data->{properties}{surveyJSON}; - $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $surveyJSON, $self->getId ] ); - - return; -} - -#------------------------------------------------------------------- - -=head2 duplicate ( ) - -Override duplicate so that surveyJSON gets duplicated too - -N.B. Currently ResponseJSON data is not duplicated. - -=cut - -sub duplicate { - my $self = shift; - my $options = shift; - - # Start off by letting Wobject duplicate the asset as it knows how - my $newAsset = $self->SUPER::duplicate($options); - - # Make sure SurveyJSON data also gets duplicated - $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', - [ $self->surveyJSON->freeze, $newAsset->getId ] ); - - return $newAsset; -} - -#------------------------------------------------------------------- - =head2 surveyJSON_update ( ) Convenience method that delegates to L @@ -356,13 +296,14 @@ will be used to instantiate the SurveyJSON instance rather than querying the dat sub surveyJSON { my $self = shift; +$self->session->errorHandler->error("surveyJSON was calle, wtf"); my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); if (!$self->{_surveyJSON} || $json) { # See if we need to load surveyJSON from the database if ( !defined $json ) { - $json = $self->session->db->quickScalar( 'select surveyJSON from Survey where assetId = ?', [ $self->getId ] ); + $json = $self->get("surveyJSON"); } # Instantiate the SurveyJSON instance, and store it @@ -1182,10 +1123,7 @@ sub surveyEnd { if ( $url !~ /\w/ ) { $url = 0; } if ( $url eq 'undefined' ) { $url = 0; } if ( !$url ) { - $url - = $self->session->db->quickScalar( - 'select exitURL from Survey where assetId = ? order by revisionDate desc limit 1', - [ $self->getId() ] ); + $url = $self->get('exitURL'); if ( !$url ) { $url = q{/}; } @@ -1308,7 +1246,8 @@ sub persistSurveyJSON { my $self = shift; my $data = $self->surveyJSON->freeze(); - $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); + $self->update({surveyJSON=>$data}); +# $self->session->db->write( 'update Survey set surveyJSON = ? where assetId = ?', [ $data, $self->getId ] ); return; } @@ -1404,10 +1343,7 @@ sub responseId { } if ( !$responseId ) { - my $allowedTakes - = $self->session->db->quickScalar( - 'select maxResponsesPerUser from Survey where assetId = ? order by revisionDate desc limit 1', - [ $self->getId() ] ); + my $allowedTakes = $self->get('maxResponsesPerUser'); my $haveTaken; if ( $id == 1 ) { From 91b49f96b061bb6b1c15d7270086c8dff5ccf95c Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Fri, 27 Feb 2009 18:39:38 +0000 Subject: [PATCH 81/90] removing trace --- lib/WebGUI/Asset/Wobject/Survey.pm | 1 - .../Wobject/Survey/SurveyQuestionBundles.pm | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 397cc5123..773609dd8 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -296,7 +296,6 @@ will be used to instantiate the SurveyJSON instance rather than querying the dat sub surveyJSON { my $self = shift; -$self->session->errorHandler->error("surveyJSON was calle, wtf"); my ($json) = validate_pos(@_, { type => SCALAR, optional => 1 }); if (!$self->{_surveyJSON} || $json) { diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm new file mode 100644 index 000000000..e1d05cd36 --- /dev/null +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm @@ -0,0 +1,20 @@ +package WebGUI::Asset::Wobject::Survey::SurveyQuestionBundles; + +use base WebGUI::Crud; + +sub crud_definition { + my ( $class, $session ) = @_; + my $definition = $class->SUPER::crud_definition($session); + $definition->{tableName} = 'Survey_questionBundles'; + $definition->{tableKey} = 'assetId'; + $definition->{properties}{} = { + fieldType => 'text', + defaultValue => undef, + }; + $definition->{properties}{} = { + fieldType => 'text', + defaultValue => undef, + }; + return $definition; +} + From 31341e5701ba0fe6fec06773f02669439eb6fe50 Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Fri, 27 Feb 2009 18:40:05 +0000 Subject: [PATCH 82/90] removing surveybundle --- .../Wobject/Survey/SurveyQuestionBundles.pm | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm deleted file mode 100644 index e1d05cd36..000000000 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyQuestionBundles.pm +++ /dev/null @@ -1,20 +0,0 @@ -package WebGUI::Asset::Wobject::Survey::SurveyQuestionBundles; - -use base WebGUI::Crud; - -sub crud_definition { - my ( $class, $session ) = @_; - my $definition = $class->SUPER::crud_definition($session); - $definition->{tableName} = 'Survey_questionBundles'; - $definition->{tableKey} = 'assetId'; - $definition->{properties}{} = { - fieldType => 'text', - defaultValue => undef, - }; - $definition->{properties}{} = { - fieldType => 'text', - defaultValue => undef, - }; - return $definition; -} - From 0db9be4512866a024679e32120a9eca6d76eccfb Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Mon, 9 Mar 2009 17:23:06 +0000 Subject: [PATCH 83/90] Added goto to sections and questions. Order of precedence is answer, question, then section. --- lib/WebGUI/Asset/Wobject/Survey.pm | 30 +++++++++++++++++- .../Asset/Wobject/Survey/ResponseJSON.pm | 24 ++++++++++++-- lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm | 3 ++ ..._import_survey_default-question-edit.wgpkg | Bin 0 -> 2138 bytes root_import_survey_default-section-edit.wgpkg | Bin 0 -> 2099 bytes www/extras/wobject/Survey/administersurvey.js | 6 ++-- 6 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 root_import_survey_default-question-edit.wgpkg create mode 100644 root_import_survey_default-section-edit.wgpkg diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index 773609dd8..4f4befd74 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -1065,7 +1065,7 @@ sub www_loadQuestions { my @questions; eval { @questions = $self->responseJSON->nextQuestions(); }; - + my $section = $self->responseJSON->nextResponseSection(); #return $self->prepareShowSurveyTemplate($section,$questions); @@ -1073,6 +1073,7 @@ sub www_loadQuestions { $section->{wasRestarted} = $wasRestarted; my $text = $self->prepareShowSurveyTemplate( $section, \@questions ); + return $text; } @@ -1697,4 +1698,31 @@ sub loadTempReportTable { return 1; } +#------------------------------------------------------------------- + +=head2 www_editDefaultQuestions + +Allows a user to edit the *site wide* default multiple choice questions displayed when adding questions to a survey. + +=cut + +sub www_editDefaultQuestions{ + my $self = shift; + my $warning = shift; + my $session = $self->session; + my ($output); + my $bundleId = $session->form->process("bundleId"); + + if($bundleId eq 'new'){ + + + + } + + if($warning){$output .= "$warning";} +# $output .= $tabForm->print; + + +} + 1; diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index ab56e6091..bd810df4e 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -469,6 +469,9 @@ sub recordResponses { my $section = $self->nextResponseSection(); my @questions = $self->nextQuestions(); + #GOTO jumps in the Survey. Order of precedence is Answer, Question, then Section. + my ($goto, $gotoExpression); + # Handle terminal Section.. my $terminalUrl; my $sTerminal = 0; @@ -476,6 +479,15 @@ sub recordResponses { $sTerminal = 1; $terminalUrl = $section->{terminalUrl}; } + # ..and also gotos.. + elsif ( $section->{goto} =~ /\w/ ) { + $goto = $section->{goto}; + } + # .. and also gotoExpressions.. + elsif ( $section->{gotoExpression} =~ /\w/ ) { + $gotoExpression = $section->{gotoExpression}; + } + # Handle empty Section.. if ( !@questions ) { @@ -487,7 +499,6 @@ sub recordResponses { # Process Questions in Section.. my $terminal = 0; my $allRequiredQsAnswered = 1; - my ($goto, $gotoExpression); for my $question (@questions) { my $aAnswered = 0; @@ -496,6 +507,14 @@ sub recordResponses { $terminal = 1; $terminalUrl = $question->{terminalUrl}; } + # ..and also gotos.. + elsif ( $question->{goto} =~ /\w/ ) { + $goto = $question->{goto}; + } + # .. and also gotoExpressions.. + elsif ( $question->{gotoExpression} =~ /\w/ ) { + $gotoExpression = $question->{gotoExpression}; + } # Record Question comment $self->responses->{ $question->{id} }->{comment} = $responses->{ $question->{id} . 'comment' }; @@ -896,7 +915,6 @@ sub nextQuestions { my $section = $self->nextResponseSection(); my $sectionIndex = $self->nextResponseSectionIndex; my $questionsPerPage = $self->survey->section( [ $self->nextResponseSectionIndex ] )->{questionsPerPage}; - # Get all of the existing question responses (so that we can do Section and Question [[var]] replacements my $recordedResponses = $self->recordedResponses(); @@ -907,6 +925,7 @@ sub nextQuestions { my @questions; for my $i (1 .. $questionsPerPage ) { my $address = $self->surveyOrder->[ $self->lastResponse + $i ]; + last if(! defined $address); my ($sIndex, $qIndex) = (sIndex($address), qIndex($address)); # Skip if this is a Section without a Question @@ -1009,6 +1028,7 @@ equal to the number of sections in the survey order. sub surveyEnd { my $self = shift; + return 1 if ( $self->lastResponse >= $#{ $self->surveyOrder } ); return 0; } diff --git a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm index 97140d484..d440466f4 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/SurveyJSON.pm @@ -852,6 +852,7 @@ sub newSection { terminal => 0, terminalUrl => q{}, goto => q{}, + gotoExpression => q{}, timeLimit => 0, type => 'section', questions => [], @@ -881,6 +882,8 @@ sub newQuestion { textInButton => 0, type => 'question', answers => [], + goto => q{}, + gotoExpression => q{}, }; } diff --git a/root_import_survey_default-question-edit.wgpkg b/root_import_survey_default-question-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..9d8572c9f20a840d3c08d49cff72c9721c309018 GIT binary patch literal 2138 zcmV-g2&MNQiwFP!000001MOP-bK5o&_Rsw*IQoTmGj;TqPpYgN*J&E};kaix>CNfR z1Cfw~gd$l2l&swJfA20pkl;g+m)S1>vX=XYUYuTRvIROB;XM){=@v0LIM<22+jKa7QixPbK%Iy(`|T-#a^FNTVt02m%u zB!c0O_Ui0Xj|yr_xb$x&tdWgjyKW~4H>$JxRq86p)T5as5sikzP2V+25~(mP##emM zVGKuc(4(?fYIkFE*MFkiJBc$}pt z#2dvQ5oQh{z!6C>(X(reL&#AxrHEv)7X)D%B(ZU2TM1Plr%pLUq36IoDrT{nrR<>Y zNdFktr7AWXLD0~FP4*hrmC#D1NjJ2xR6ndM;kk+W_;VZ}RaGtwdN`>uuz8u(=CBVs-^A>kIFnk6%FGLh7OHAtvkh$+?V|`GOw}Y-pF4ps2!nknNT^;YbH;uWo9h00 z>K4gS&9Tb;g!CwV3M0ySRi0&cAbmfBrzarzL6L*R&GQYX_1%2F0&rJt|_=C)x8 zz9CoAXrCVukVva9g@ZCgCO0Mmy{0`0V%&5eIuYyzTF6+vX1=l~$2NG2Gt} zSd0`KV&8)aYHc+})sISU@^>r*nkDFZe4a%G0!Q;D%s`9yd`+W0AV5_Iv7xBqh+$;A zH?-2LWLSC=m!n6a(PyDaL1)U_5?u-nOU50GHWdp__RxpQnR&oT~Jy{NAN>Rg~ zBZ>-~(uS=x(HdW>Q*G)DlO=YsQOOgy%|CK9DeBbpEwW^{amS*`bD|4h1ZxY|J-Caq zrm+xJ_YkIF^cFsc#Fq*srzZ{$>c;<84M{u4WR2P2a}dVVm(sY%7PC>wsACYt^yR*B zg?O-cvz%bLg!{|m2bG-o(-T9gyG{mEMGUh+$p;5Tl6?Z4sckDQ%tj@XjzJ5Y($Aqr zLJA;pZg zI7Y$x6F8=PIwbm{$bCV~6jYs;p9A1OKzk8DEVDw6Ko}!^*yiLSnOUe4k2?+^r1-SQPwy}q!Hfi=D|*)LvmTDt~NSd zyg^7VHXvs&uM^abgtlr7QgT-eD5vUTVOYoe6X}-BsO(pkdu0BCMoc_J-m55B-hqj} zN>CcPOAl_rlJs#+em+p~K|v-_UM$^0&QRt#-_hy!4>yB>%smbUW1Z^EXO6EI#Gh|V zJO3QGY{np=tn#MqUa#5jw)>ez!Gx!@y;ONy>z5>NYsq|Tsw+vtzGYOUqAxNTjAK(w zI2Dt+`~wSb0|6#DGLb6l4Rm175|8(_0Q2OrOk3AT2s!wbnJ1UY>w7qfOBENbj*)*4 zNd)O|S+NDu!Ql1y=by>|GyYS67auC8pUc`ao&Z)HlT7C)PLXx}{zbpvy=WQZj_8Wv zo#RWLxo)<~u3`L+I3F~5j8r*p*>C(;KvIQS01v|=2><{9 literal 0 HcmV?d00001 diff --git a/root_import_survey_default-section-edit.wgpkg b/root_import_survey_default-section-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..0fb21ed35a30ff4de64956f0bc9b32c749ce9baf GIT binary patch literal 2099 zcmV-32+a2%iwFP!000001MOPtZ`(Ey&*%LWguiG5mRj-?@f;6b*N3q-$(D2v%v+#H zjLlY-G?I$r4*TDCM~V_{$%)g%*$UJ^VoN0Nj=y{G$m5f}-G5xy?RUF-d+_b{-13_g zA9v65yl%JEbvvE*9`QW4)!HL4WcQy;NS}jfe(DD^x|!c??Kn9%pB#RFA|EBSSN?Aw zsNel3??e(m&>VnbuhS{@jvUYqwEA0KyWL;Yf2ZB*?~zAAaaa0(@<8D2n9Sc_fEF4; zCfuLW1{sjw|M*(HN_mn}E*Ta0R&~Rk0jIvC;de`X+h{jbXKaXkA)jBT35V9FYD}C( zQPB)uCKdfgnq5Rp%xGASjp^bS7Kh0~5p~N3v5b>cuqwG^5mWwFP(BXV1)&`U zp83=2Kwk(d^Fdzn-A^Jo8V4_q$NTB?Gyd#Yq)$uk8IKfko+Pr#<~X;e$oLgqHbZ*p zXOY|&G>|Nb_i4yvIgp80EDUL^Mtk=A6Q0OKE>jJKHPb0ivUD6iM>>eC*D8leI!_}5 z-!NoXgoTGr&e|DG_#{?u1JHpubY`J5`g0sh%3~je^P?}FG@`zsgyLJ0k0dO& zalUq*A~NTvYJSdF-wd0;s6zV!Xd^##-64)9u|KDW4*qn=oXS}OqMaO{o;k!<&>TA7 zUS@G{D6)$=lgAhD05P1UMTf;{CJCYf!YkmMoGc3+a^*)EGz=wxaDL@;nEdFL!3a-C zBPyvOWS%rJx12*AZyX*Z>Cz0=zt@KrFi8I*qNAbY@E0n|rb(fc7R>MwKd7vVhKy3; zY<@v`RUbA@O-2(UTI-X>V!A&|t|*_;D4jHJp-)080?tyjk1DTH_lA=aTUi7t6%jH4 zcDbbCIjF>zZ`dp+z4E?%Z3y3t1=vgk zU>_hVcy5lRgJ9MGZV&*>wPwrG_ssHIoP zWke4rjTvRrnH-Rgn_fc@woOO(-VpuYW%HECWIH1%xNaHxbK)Ok9g0%lqUw{s>LkK@dZNa`FpUF8UlH<^_odpZ!Y(QFWFTMVT82{?F@d?u% zu&u>_=(KTz(uZg`K5SfU(L-vzHLc@PiQMeELxor7?ELL2?te(jBOa3 zz;B~V{Dw%>ge21>nxY7PTRR3vcxR! z1=}CZ3T%=BuUu8Cokj!>e}eZkB-Zk>zKL2=aXU_V_|m(XEk$#4(Xu!KhbCKyr+bbn zS$=%W5@PI2uWQD9Y9dc<*eMowlElR>a@Wbt8ms#oQJ&-ESw!gr$MLD&&0~^N_^R%W zNNv_^fy)Ys((sIJRn&E(MctpXTVZ&Ueb~6rNIWP66+w1Ab5b~Th4oDs6dq9|nI`%w z3&{7irT3J-RQr|zSnUGQhbSyy%N=bGYM4B@?H`$L7t4&4EGeWW+Q$VYYti{pRd3l{&Kx`M^W z{U3V*pEEeJH|f>w38z|ka96cuVh$C66%G&X1%|<$9KZ~E6x&~C_K0S`&UyzHzs};* zF?IqKw?{B|;BtSXH_M&9E5MDuK4UOCcnhqmMS$D`q0y>jDL=LfC-`jEQoZSK>PnM& zX};HXZ?!u&Q(Zy{Z%6np`+S@7T~S><|G>RzIb(BZV$N*v8BS`-D!`=Ca=X3CNl~YZ z2Qv9N5nOCgr!)G1Cl~chQL+wfkaXQKO#yHdPo-2iD<#&y$|5a`XOuGuI(@;D`3W31 z#k!Jf6?r2#SA=9)6?oM(i<*lUB}Em_kDy4U`u{>NUcMa<2C7Uu7@V1!@M?wq+02ip zo67TVl8{|8D7@y|?5bGS618a4nr7`!@r^STNz6R#6IAksGF-sQX_yw4!M#E~tV{!qPnluc$uHRhMBaH*T zOeJpM)aJ5dPKf{l>DVFBjwPqSPxBr%M4>6xJ!Ii5a}?n<8K$ z>YhlQk*Mn>t7x=(u6xkySoa}7iQrthc-@Qm=!2w3{YCpI{^4N$qVp?zHf`V?mVdtZ dXUDf^K9m29z-I(LBk<`W@GnZRf#Cow007C#`Sbt) literal 0 HcmV?d00001 diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index ea165c431..bf837e1fa 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -318,9 +318,11 @@ if (typeof Survey === "undefined") { var qs = params.questions; var s = params.section; sliders = []; + console.log("Can are farking at least get here?"); //What to show and where document.getElementById('survey').innerHTML = params.html; + console.log("How about farking here"); //var te = document.createElement('span'); //te.innerHTML = ""; //document.getElementById('survey').appendChild(te); @@ -333,7 +335,7 @@ if (typeof Survey === "undefined") { if (lastSection !== s.id || s.everyPageText === '1') { document.getElementById('headertext').style.display = 'block'; } - + console.log(s.questionsOnSectionPage + " wtf"); if (lastSection !== s.id && s.questionsOnSectionPage !== '1') { var span = document.createElement("div"); span.innerHTML = ""; @@ -500,4 +502,4 @@ if (typeof Survey === "undefined") { YAHOO.util.Event.onDOMReady(function(){ // Survey.Comm.setUrl('/' + document.getElementById('assetPath').value); Survey.Comm.callServer('', 'loadQuestions'); -}); \ No newline at end of file +}); From 4ecc0b76796a6ab510e9b1b41494dceccee08c1f Mon Sep 17 00:00:00 2001 From: Kaleb Murphy Date: Mon, 9 Mar 2009 17:23:33 +0000 Subject: [PATCH 84/90] removing traces --- www/extras/wobject/Survey/administersurvey.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/www/extras/wobject/Survey/administersurvey.js b/www/extras/wobject/Survey/administersurvey.js index bf837e1fa..a91b42696 100644 --- a/www/extras/wobject/Survey/administersurvey.js +++ b/www/extras/wobject/Survey/administersurvey.js @@ -318,11 +318,9 @@ if (typeof Survey === "undefined") { var qs = params.questions; var s = params.section; sliders = []; - console.log("Can are farking at least get here?"); //What to show and where document.getElementById('survey').innerHTML = params.html; - console.log("How about farking here"); //var te = document.createElement('span'); //te.innerHTML = ""; //document.getElementById('survey').appendChild(te); @@ -335,7 +333,6 @@ if (typeof Survey === "undefined") { if (lastSection !== s.id || s.everyPageText === '1') { document.getElementById('headertext').style.display = 'block'; } - console.log(s.questionsOnSectionPage + " wtf"); if (lastSection !== s.id && s.questionsOnSectionPage !== '1') { var span = document.createElement("div"); span.innerHTML = ""; From 5da0379f99879abfd999b8f0da2c534b90c39b01 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 15:29:04 +1100 Subject: [PATCH 85/90] Updated SurveyJSON tests to recognise goto[Expression] on Section and Question --- t/Asset/Wobject/Survey/SurveyJSON.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/t/Asset/Wobject/Survey/SurveyJSON.t b/t/Asset/Wobject/Survey/SurveyJSON.t index 2470fca5c..42f68a3ef 100644 --- a/t/Asset/Wobject/Survey/SurveyJSON.t +++ b/t/Asset/Wobject/Survey/SurveyJSON.t @@ -2151,6 +2151,7 @@ sub getBareSkeletons { terminal => 0, terminalUrl => '', goto => '', + gotoExpression => '', timeLimit => 0, type => 'section', questions => [], @@ -2171,6 +2172,8 @@ sub getBareSkeletons { textInButton => 0, type => 'question', answers => [], + goto => '', + gotoExpression => '', }, { text => '', From 8c5436b7d2193be2876ecb733519d86b66bb88de Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 15:34:36 +1100 Subject: [PATCH 86/90] Added Survey upgrade script for doAfterTimeLimit and RemoveResponseTemplate --- docs/upgrades/upgrade_7.6.14-7.7.0.pl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/upgrades/upgrade_7.6.14-7.7.0.pl b/docs/upgrades/upgrade_7.6.14-7.7.0.pl index 65e25aa6c..c915b2fe1 100644 --- a/docs/upgrades/upgrade_7.6.14-7.7.0.pl +++ b/docs/upgrades/upgrade_7.6.14-7.7.0.pl @@ -34,6 +34,8 @@ my $session = start(); # this line required addGroupToAddToMatrix( $session ); addScreenshotTemplatesToMatrix( $session ); +surveyDoAfterTimeLimit($session); +surveyRemoveResponseTemplate($session); finish($session); # this line required @@ -61,6 +63,26 @@ sub addScreenshotTemplatesToMatrix { print "Done.\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub surveyDoAfterTimeLimit { + my $session = shift; + print "\tAdding column doAfterTimeLimit to Survey table... " unless $quiet; + $session->db->write('alter table Survey add doAfterTimeLimit char(22)'); + print "DONE!\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub surveyRemoveResponseTemplate { + my $session = shift; + print "\tRemoving responseTemplate... " unless $quiet; + $session->db->write('alter table Survey drop responseTemplateId'); + if (my $template = WebGUI::Asset->new($session, 'PBtmpl0000000000000064')) { + $template->purge(); + } + print "DONE!\n" unless $quiet; +} + + #---------------------------------------------------------------------------- # Describe what our function does #sub exampleFunction { From 0e9a551bc9f460821ce3b9fa22e45297a5be1d30 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 15:35:24 +1100 Subject: [PATCH 87/90] Added Params::Validate to gotcha.txt --- docs/gotcha.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 387085911..4e0e2a486 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -7,6 +7,10 @@ upgrading from one version to the next, or even between multiple versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.7.0 +-------------------------------------------------------------------- + * WebGUI now requires Params::Validate version 0.81 or greater. + 7.6.11 -------------------------------------------------------------------- * If upgrading from WebGUI 7.5, you should upgrade to 7.5.40 first, From 1f6ee6f595f2452df2271e3f436e0ccc1463c30a Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 15:43:20 +1100 Subject: [PATCH 88/90] Cleaned up Survey i18n --- lib/WebGUI/i18n/English/Asset_Survey.pm | 81 ++----------------------- 1 file changed, 5 insertions(+), 76 deletions(-) diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index 03f5efd61..7229209ff 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -535,16 +535,6 @@ the time limit for completing the survey. This message is in the 'take survey' t lastUpdated => 1233714385, }, - 'Max user responses' => { - message => q|Max user responses|, - lastUpdated => 0, - }, - - 'Max user responses help' => { - message => q|The maximum number of times an individual user is allowed to complete the Survey.|, - lastUpdated => 0, - }, - 'Overview Report Template' => { message => q|Overview Report Template|, lastUpdated => 0, @@ -574,56 +564,6 @@ the time limit for completing the survey. This message is in the 'take survey' t message => q|The template used to display the Survey Edit screen.|, lastUpdated => 0, }, - - 'Take Survey Template' => { - message => q|Take Survey Template|, - lastUpdated => 0, - }, - - 'Take Survey Template help' => { - message => q|The template used to control the initial Take Survey screen, from which responses are dynamically loaded into.|, - lastUpdated => 0, - }, - - 'Questions Template' => { - message => q|Questions Template|, - lastUpdated => 0, - }, - - 'Questions Template help' => { - message => q|The template used to display individual questions, which are dynamically loaded into the Take Survey page.|, - lastUpdated => 0, - }, - - 'Section Edit Template' => { - message => q|Section Edit Template|, - lastUpdated => 0, - }, - - 'Section Edit Template help' => { - message => q|The template used to display the Section Edit dialog on the Edit Survey page.|, - lastUpdated => 0, - }, - - 'Question Edit Template' => { - message => q|Question Edit Template|, - lastUpdated => 0, - }, - - 'Question Edit Template help' => { - message => q|The template used to display the Question Edit dialog on the Edit Survey page.|, - lastUpdated => 0, - }, - - 'Answer Edit Template' => { - message => q|Answer Edit Template|, - lastUpdated => 0, - }, - - 'Answer Edit Template help' => { - message => q|The template used to display the Answer Edit dialog on the Edit Survey page.|, - lastUpdated => 0, - }, 'Max user responses' => { message => q|Max user responses|, @@ -658,17 +598,6 @@ the time limit for completing the survey. This message is in the 'take survey' t lastUpdated => 0 }, - 'Response Template' => { - message => q|Response Template|, - context => q|The template for displaying responses to the survey.|, - lastUpdated => 0 - }, - - 'Response Template help' => { - message => q|The template for displaying responses to the survey.|, - lastUpdated => 0 - }, - 'Edit Survey Template' => { message => q|Edit Survey Template|, context => q|The template for displaying the screen for editing the survey.|, @@ -687,7 +616,7 @@ the time limit for completing the survey. This message is in the 'take survey' t }, 'Take Survey Template help' => { - message => q|The template for displaying the screen where a user takes the survey.|, + message => q|The template used to control the initial Take Survey screen, from which responses are dynamically loaded into.|, lastUpdated => 0 }, @@ -698,7 +627,7 @@ the time limit for completing the survey. This message is in the 'take survey' t }, 'Questions Template help' => { - message => q|The template for rendering questions in the survey.|, + message => q|The template used to display individual questions, which are dynamically loaded into the Take Survey page.|, lastUpdated => 0 }, @@ -709,7 +638,7 @@ the time limit for completing the survey. This message is in the 'take survey' t }, 'Section Edit Template help' => { - message => q|The template for adding or editing sections.|, + message => q|The template used to display the Section Edit dialog on the Edit Survey page.|, lastUpdated => 0 }, @@ -720,7 +649,7 @@ the time limit for completing the survey. This message is in the 'take survey' t }, 'Question Edit Template help' => { - message => q|The template for adding or editing questions.|, + message => q|The template used to display the Question Edit dialog on the Edit Survey page.|, lastUpdated => 0 }, @@ -731,7 +660,7 @@ the time limit for completing the survey. This message is in the 'take survey' t }, 'Answer Edit Template help' => { - message => q|The template for adding or editing answers.|, + message => q|The template used to display the Answer Edit dialog on the Edit Survey page.|, lastUpdated => 0 }, From 505398682c0fd0f0bf892840ccd6c5fd2095a75f Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 17:23:56 +1100 Subject: [PATCH 89/90] Merged Survey Templates --- ...root_import_survey_default-answer-edit.wgpkg | Bin 0 -> 1846 bytes ...import_survey_default-gradebook-report.wgpkg | Bin 0 -> 1269 bytes ..._import_survey_default-overview-report.wgpkg | Bin 0 -> 1693 bytes ...ot_import_survey_default-question-edit.wgpkg | Bin 0 -> 2137 bytes .../root_import_survey_default-questions.wgpkg | Bin 0 -> 2628 bytes ...oot_import_survey_default-section-edit.wgpkg | Bin 0 -> 2076 bytes ...root_import_survey_default-survey-edit.wgpkg | Bin 0 -> 1593 bytes ...root_import_survey_default-survey-take.wgpkg | Bin 0 -> 1041 bytes .../root_import_survey_default-survey.wgpkg | Bin 0 -> 1168 bytes .../survey-root_import_prop-style.wgpkg | Bin docs/upgrades/packages-7.7.0/survey.css.wgpkg | Bin 0 -> 1627 bytes ...import_expireincompletesurveyresponses.wgpkg | Bin root_import_survey.wgpkg | Bin 10634 -> 0 bytes root_import_survey_default-question-edit.wgpkg | Bin 2138 -> 0 bytes root_import_survey_default-section-edit.wgpkg | Bin 2099 -> 0 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-answer-edit.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-gradebook-report.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-overview-report.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-question-edit.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-questions.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-section-edit.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-survey-edit.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-survey-take.wgpkg create mode 100644 docs/upgrades/packages-7.7.0/root_import_survey_default-survey.wgpkg rename root_import_prop-style.wgpkg => docs/upgrades/packages-7.7.0/survey-root_import_prop-style.wgpkg (100%) create mode 100644 docs/upgrades/packages-7.7.0/survey.css.wgpkg rename root_import_expireincompletesurveyresponses.wgpkg => docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg (100%) delete mode 100644 root_import_survey.wgpkg delete mode 100644 root_import_survey_default-question-edit.wgpkg delete mode 100644 root_import_survey_default-section-edit.wgpkg diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-answer-edit.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-answer-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..763d5e2e3508e9f50b9c2aaddf66db8040e1bf58 GIT binary patch literal 1846 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 literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-overview-report.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-overview-report.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..d152cf52d024ac8603eea8e0a05ea7820086c1bf GIT binary patch literal 1693 zcmV;O24eXiiwFP!000001MOICZ`(Ey&gcFLPQaEHX#5(-vFij_oMthWX4#Uy0MlmB z5@WNLNtLARx?TSJj-({Yik-Aw1MEWt*p_w2yW?|jJXN+H{;Jh#?N)1Rt5&PGTQ&EK z@~gI0uh(0xy;h^yY;7U9ukURkSn}{!A{7>3T8x#9@ob~Q`{=Mqxb)`i??yrYFM_eQ z?!KEHeP7?NyqBDPCsRS--tMmRdlm=Q{qNnRM3Gsd$$=mkC`U`Z z$g6uf0kTXP`CgoiA}S{&bVNoLZz&7;!mO$}f?P5lOL|)m`KSawB{;moLWL8is_GAsP#w#DnlNwx4hRtT7o{!Q0PX^s2ZZ{vWWTY7C{<*0}UfZq*f@lH4j^ z-L^*o!`=31j(hC!)%}*d0gwM-94@t~YfRBGF_aK95g7_1AyniBh%_g%oG9ho1QnRJ z#tTLjL^zW%mn8fWb&mT#sMJ|XrJ@0jhFKcixkty3GX8Ja=~&>93`d+#?^0&CkmlN? zq8Mzws!aq4!I3~!@M-o7cQOz(R!C>(f$u4(Ce`xc(^(f-O)c0N(18ztp3E1)ta;z1p7@ytvtXkdK(Oa)tTy$Eb zbtU3FMjsLap~Bfv|Ib3dzh*&-T6syf z`6%LxKA-6%&8<#tNL!=txysOWX}=xVRjzYpK!%6V2iJt0obUxihtVZBdl8pRE%r|f zq4RO(0&ruVQ+U&s1fn5i#T|A-;mdL-txsKD6!d0vcIO_1T|_V|F_6|Q(eju})vN}c ziGcW40DxDm2YOXoM^a68>v?s_pJusf#I2xF0xONbf&_S?tBKjqt?$STG)!;x`!F%StPjQag4{*o^2~|OZvKcS6pAR zfuga`x{+IuhrZjGBUWNT=9DM$0;C?IpJs66=jsJo&kH?qg?gUNK@# z&rQ!cMGV~BK-q){s$PreMDW=K7EtKSa8Z~?BFacQ{CQ@US@Sx;Ni>68e}dUKAM*-h zk3s1i&+O3imW+ORJ?M7LkkIX3WkXCp73yj76%Xk(g>leKjE&}AyH#&B3Zr8>7wH4K z;b}W#%6b95^8grti@nPSGiOZv$Yn)Uqx zb{dVu44@v4VPu{c$6Dwr$(5eYzRbcH3z*dm?*`zy-P5aAXSZRb@(j*^nLkzraw=0~ zjNFptaO^UEG_+2q-RZR2jRKDjVF@QZIDDn3tZ@%ax;U8T0pt+X4R63sxR_ah3NB?3 zA<#hrE7cBw)y#EH0%3tz#wOOw<|{l-C-F0S^zNj!%a^Qk{AT+5>1=s9?Jt}VDleB3 z=I62maJYGovCidY;~AdC*BIvgMhTJKMst6^*>M`dk&tGod94vU|D?!yd(k{+zjkIP nyYFa!?CEzbH&1T9$19vi8IK4&BJha7HzDvZJ8C~Z02%-QdlWjv literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-question-edit.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-question-edit.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..ccc7491ec86e298560bdcb851ef21db1ea9b2434 GIT binary patch literal 2137 zcmV-f2&VTRiwFP!000001MOOEbK5o&_UHZzjK1)0rjA}LT4Pn#P2x0-?@}kNotMt( z&I6HF;z8TD`I zwAvdjLYBN2EV=ZR#FIi|ngpsTi6YtLb6iMMq{%&7HhngsX&{e(PnnQB3Xhr3WigWq z!DJ?I@!aduFaP|&KitI8ap&r{x1T=VPm8f!yh|b($z`n9Y%Ph4*WCA6SO#dCL}`5K z|A;yjcAY|;Wb-&M_zir1Pq=^X7(1QeC{&FeI7gg2Gv68h_cD|$2`R3F27hznfKkB+ z!&YJ@5*|*XWUdA|-#gE7fw$KR4Cn7Z44R;*Bzg>@MNQ>a&1J<|vTNcP*-AtY5F!i{ho8s=t*cN;(dhks_1fK>FD)jf=u2&YJKC zKIol_OL8xS>3qzRssjpvT8frKw3kPX#q@X<-LquIf_T)}B%b(8cnOcuD^*p-*G7>t zI`jWZTkZ*;3!vnZ`M2Pc!qP$0Uc1$9H!JCnLIS?#2(;ot)!ag^G0x)u_Q@Lv=4O>6_vGhF0AbkZ06N_m zKd?wEcyP&&Ksl0?P|5j&d#sSlfSr#TGsdSg*(cpr`~Zozh%|e2CI5a-=P{AdHkd08 zqQ)&#dnS5!z?Gt!v~CuX8v@6RQDX`xnB!&2kEu6mbdjUtlJg5hR02wkouv-sGHLP>*B=5Vf1}s@&WsyO5(0zdK247o0;3R>Nj?i=KgNK(>ZkJON(%7E2b~ zm$N?Uv`*uPABc${_XL08p#@m9=o5Y7t99@IDMH~kVUwiL2&!_A7p;%j0kLb@fxnO! zdCp#44-m$vFNJ#<9K(c>L_cZI!DK%>7mg(SQ|D#n#!9{iSSyJmVZWz5VLl1ym<8M9 zk6jKwx%B;<2kmooLVX^Yql?j#A9LE)K59CL#G5hi4%TXIq~)v2l8H?q47N7|HWTgj zGz=gB$y!r%|FGho@X?h1ZQFH1;SxND_#!kE3zlqL_^!#JqF_(yVLkq4MbEPZnM2kJ z9ZG}#1X`3d<}9tyqcpH$+CFGf**a?X4al03do&=~)oW8w?8=R@{I92mPKHYVT4ES& zFZDe#cYw_cr#z^FJ|4XHnlH*44PDE;*lgN9h_UO|A!gBn;LZhacE~uDvhJ>52t^O! zv&;TbqvEhc!R1_rFZ5!JJ8YKN0}f9RTU$=rGCS-+6_dV|7={0~%NsO+vlj89`5fN< z?K^3#2tORnuL%8~Ao!KSFy5b;o+hrek6Uyf)R0`BfZW2un zb+lcpXghQb8Pv+E023QwNISVw_dK3*ennRw`{2 zW%HIQp_}4)AyJh*FJ?5H=0#Df7^z-TR=(J!x|V(s}RhW8rIe9E(;M{&0z5;q`cr16~D^f_to(5DGXPc|uVxgBTWBIN|u%y$hV*Q4{ z`1H!{M$7Q*<%hdpUeA};cNdGo1TL{w>Y^9|8+Kh)&0aTrVac{gg?IfV;_I$@Fe32fhPo>5O_jh P1%dwqy9)6W04e|g$$2o> literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-questions.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-questions.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..0465335bf221a465c22694995d21b2757f0ab19e GIT binary patch literal 2628 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 literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-survey-take.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-survey-take.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..a31b0c6f6f8324ac8a21241a33c131e4ae8e0574 GIT binary patch literal 1041 zcmV+s1n&DEiwFP!000001MOB@kK#5M<#~Tal&4lIlLQDXlpSezE*;bXv4z=PwX`b} z8}N|Wk?p`{tNri&YzGqNqE_lq^#MyHk-wXL&gWy!YR4bG?{~tmR`dO!6Z+-b*$=-K z1VI?K!$z|i)*Sc_+BFA4jz2P~kbtG7NXZl>&ldbUJ8Tk$-kg0uo9O>#Fslv9XS1un zgT~pt}LW4+{EQ!2JXMTb(xW4`?2L9E<<6<1hUsD*X8hSa7A{0xgN_ z^qk**|G`Y9f~Q0%N~E4OZrU?OQeAyXxljbZH71tDu?f-UNDu^;cexI719xwLQ>ILQ zH_hfTl`DcvBO}|7lwrO#QGRKVIpe9MkL!>xN}wBp@DweL)$PZWtsOz)H#68y9up`* z)Xa5Mo)VZ-5aLBr!pusL1T`tH6!ttZ$bx5KG~N)QOmZ0snyQ2Lx!Esx*cYARoaze z*rfF{D=$Db4jLvs_PHrbvjVY|Azz1^ei|dSayBT=$eX@FdxL}n9eQpsw_KHdK-r22 zs_sO5EqF3Uf-u`G73lyuAcds4<@M&9t8RD6l^KmZU91pW?!~KeR7{tRP-2_-Bbonn zH|X`=>eJloO?OSX*E+3{LyqZ!g2)*iY&6@QFbEru`}_kXR`ojNViZ-eEp=%RqGP+p z(M?&pLBFRXuAoeW6y=Xj5|#Q8p?Q(3Qrkd2ppfilTn97zb?~p6PB9W(R{6u1fmW7($ z(s!S(!xqn3_wvK~{Y{cj))xnAI>|vYl82iIqRox`6xC+aJ1nagvbm`rpwVhHUw4}o z$bdvRTE+6hIU9XdWYpO{W+;Do>lffEA%83KO+ LDDZ#q01^NIK}Q36 literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/root_import_survey_default-survey.wgpkg b/docs/upgrades/packages-7.7.0/root_import_survey_default-survey.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..e3aee292b002d4500febbdee16bebea82b7ba456 GIT binary patch literal 1168 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 literal 0 HcmV?d00001 diff --git a/root_import_expireincompletesurveyresponses.wgpkg b/docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg similarity index 100% rename from root_import_expireincompletesurveyresponses.wgpkg rename to docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg diff --git a/root_import_survey.wgpkg b/root_import_survey.wgpkg deleted file mode 100644 index 21d1c03d54eefb29f91e211b5924333bc1f1802f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10634 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 diff --git a/root_import_survey_default-section-edit.wgpkg b/root_import_survey_default-section-edit.wgpkg deleted file mode 100644 index 0fb21ed35a30ff4de64956f0bc9b32c749ce9baf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2099 zcmV-32+a2%iwFP!000001MOPtZ`(Ey&*%LWguiG5mRj-?@f;6b*N3q-$(D2v%v+#H zjLlY-G?I$r4*TDCM~V_{$%)g%*$UJ^VoN0Nj=y{G$m5f}-G5xy?RUF-d+_b{-13_g zA9v65yl%JEbvvE*9`QW4)!HL4WcQy;NS}jfe(DD^x|!c??Kn9%pB#RFA|EBSSN?Aw zsNel3??e(m&>VnbuhS{@jvUYqwEA0KyWL;Yf2ZB*?~zAAaaa0(@<8D2n9Sc_fEF4; zCfuLW1{sjw|M*(HN_mn}E*Ta0R&~Rk0jIvC;de`X+h{jbXKaXkA)jBT35V9FYD}C( zQPB)uCKdfgnq5Rp%xGASjp^bS7Kh0~5p~N3v5b>cuqwG^5mWwFP(BXV1)&`U zp83=2Kwk(d^Fdzn-A^Jo8V4_q$NTB?Gyd#Yq)$uk8IKfko+Pr#<~X;e$oLgqHbZ*p zXOY|&G>|Nb_i4yvIgp80EDUL^Mtk=A6Q0OKE>jJKHPb0ivUD6iM>>eC*D8leI!_}5 z-!NoXgoTGr&e|DG_#{?u1JHpubY`J5`g0sh%3~je^P?}FG@`zsgyLJ0k0dO& zalUq*A~NTvYJSdF-wd0;s6zV!Xd^##-64)9u|KDW4*qn=oXS}OqMaO{o;k!<&>TA7 zUS@G{D6)$=lgAhD05P1UMTf;{CJCYf!YkmMoGc3+a^*)EGz=wxaDL@;nEdFL!3a-C zBPyvOWS%rJx12*AZyX*Z>Cz0=zt@KrFi8I*qNAbY@E0n|rb(fc7R>MwKd7vVhKy3; zY<@v`RUbA@O-2(UTI-X>V!A&|t|*_;D4jHJp-)080?tyjk1DTH_lA=aTUi7t6%jH4 zcDbbCIjF>zZ`dp+z4E?%Z3y3t1=vgk zU>_hVcy5lRgJ9MGZV&*>wPwrG_ssHIoP zWke4rjTvRrnH-Rgn_fc@woOO(-VpuYW%HECWIH1%xNaHxbK)Ok9g0%lqUw{s>LkK@dZNa`FpUF8UlH<^_odpZ!Y(QFWFTMVT82{?F@d?u% zu&u>_=(KTz(uZg`K5SfU(L-vzHLc@PiQMeELxor7?ELL2?te(jBOa3 zz;B~V{Dw%>ge21>nxY7PTRR3vcxR! z1=}CZ3T%=BuUu8Cokj!>e}eZkB-Zk>zKL2=aXU_V_|m(XEk$#4(Xu!KhbCKyr+bbn zS$=%W5@PI2uWQD9Y9dc<*eMowlElR>a@Wbt8ms#oQJ&-ESw!gr$MLD&&0~^N_^R%W zNNv_^fy)Ys((sIJRn&E(MctpXTVZ&Ueb~6rNIWP66+w1Ab5b~Th4oDs6dq9|nI`%w z3&{7irT3J-RQr|zSnUGQhbSyy%N=bGYM4B@?H`$L7t4&4EGeWW+Q$VYYti{pRd3l{&Kx`M^W z{U3V*pEEeJH|f>w38z|ka96cuVh$C66%G&X1%|<$9KZ~E6x&~C_K0S`&UyzHzs};* zF?IqKw?{B|;BtSXH_M&9E5MDuK4UOCcnhqmMS$D`q0y>jDL=LfC-`jEQoZSK>PnM& zX};HXZ?!u&Q(Zy{Z%6np`+S@7T~S><|G>RzIb(BZV$N*v8BS`-D!`=Ca=X3CNl~YZ z2Qv9N5nOCgr!)G1Cl~chQL+wfkaXQKO#yHdPo-2iD<#&y$|5a`XOuGuI(@;D`3W31 z#k!Jf6?r2#SA=9)6?oM(i<*lUB}Em_kDy4U`u{>NUcMa<2C7Uu7@V1!@M?wq+02ip zo67TVl8{|8D7@y|?5bGS618a4nr7`!@r^STNz6R#6IAksGF-sQX_yw4!M#E~tV{!qPnluc$uHRhMBaH*T zOeJpM)aJ5dPKf{l>DVFBjwPqSPxBr%M4>6xJ!Ii5a}?n<8K$ z>YhlQk*Mn>t7x=(u6xkySoa}7iQrthc-@Qm=!2w3{YCpI{^4N$qVp?zHf`V?mVdtZ dXUDf^K9m29z-I(LBk<`W@GnZRf#Cow007C#`Sbt) From 92a5c7eaa76cdc7184de913048701c9f12b3fc92 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Wed, 11 Mar 2009 17:28:25 +1100 Subject: [PATCH 90/90] Turned off package flag on expireincompletesurveyresponses --- ..._import_expireincompletesurveyresponses.wgpkg | Bin 0 -> 1155 bytes ..._import_expireincompletesurveyresponses.wgpkg | Bin 1138 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/upgrades/packages-7.7.0/root_import_expireincompletesurveyresponses.wgpkg delete mode 100644 docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg diff --git a/docs/upgrades/packages-7.7.0/root_import_expireincompletesurveyresponses.wgpkg b/docs/upgrades/packages-7.7.0/root_import_expireincompletesurveyresponses.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..eadd26f7ff41dc28c579537743d0d466c9f8e6c8 GIT binary patch literal 1155 zcmV-}1bq7+iwFP!000001MOE^Z{j!<=6QZa#Pe($E=gK6szh&Fq%viIZq?aYRVF!r zr@@YFhn9}!zpw2i*tEdV8LD0FVu=v>a(vErZeR9FZ$Dhu4Lq+@a@|Vcx#r)Q54TjQ zR6Ni3>b_qqIr6<)FFEqa+mDnB#H4Bo5NEe>gTN~}63w?CZ{`2Jw^w}|H+*|1y-?%E>z7)^i)3|!6XZE9!8011eWXKJ=wE9=c_Ax2!BJtmaN)uui}(m0+k%PbRG zemO}8G3H~4%vk~tH<(29K?`-w0-q6@aQspneIUTDIj~mrToHH_3|#<<`e^j>AmSWE zen_f|8MH{)1)hjW606X!nA=kpYvGJiv5zOpwtdiNFbS#L4+40~rZ8ht03{swkJgQF z%r9^h0ns;Dmtx*wR8TQXGPo=>MvSIOD>{p?$WB#_H~~_h0^(CZ%=w@!x|_tmExKqE5Ks;a%lWK71uu()RNIir&f zVn8xnEXZO^AP?+vR@lt5D0N0+C_&-sCJpZm41T|EH5yG7GL1&RKzK2xPsWIhRwYE6 zMtF#2sHgm zzsJc(Dx@+Y5}0ODXW=vfl4_5>P8XHcsZO#oN@T^D+F7krt%sj7`+V0kiYAE3++Bxq zqjAx1U%tc~^Gh1u7kDp}7|c&$h|>5)5a`;I`$^w85kDwM23?j7U3(s3?yW;M0A#1CeSD^U}*Z(?xN9Y_cEDw zI=8o^`#C!KWYogEH{-H0XrrGSTVQAxaT!3mgf9yrUHP9OR6nR5Ja??j!d$&>ui3s2 z{hLpdVfU=ozhafUTKl1_+ROKM-?!1db~1hy0aFfL%>uppHkn_hoxEcm|EmU)94_+z zdads6_}{Pkc9Zk3<$n?8l4s0Judqtpe~BBes9p2k{}bHM)MYN2c*!K0l7wZzCgQ6%|sc z*o>1g8i0`8IF7RqTMH`947Wv=766hg*pQ{pvF6Jr+0>hbX2LMh)V{Y_vdV_KV_KVZ Vz}=o50{>qG{ssXsqxS$90042QF_i!S literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg b/docs/upgrades/packages-7.7.0/survey_root_import_expireincompletesurveyresponses.wgpkg deleted file mode 100644 index 6ba373af7df93d78b020db8c84b89cc485be5033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1138 zcmV-&1daP2iwFP!000001MOJP}nthV=~b#*v*aX5AVk6hYo&wL&PdtW#u zTlHRQ51LIAcV$iG!6N*PP%b`>zaIo%&G|=Ae5v^FZ56^7Q}*{O8HG9*l%X-yowoDG zpRe_+l+hHJz`)g8Ev6)6%+mzr11u6PUia!+Uadt8kbwAIu6ERYvydPz&K^?ANSi}F7gBhAib7>SOCy_C**hC7^;uJ(uc_e7#7zo zIcId*Lkviv*pi8XM+~O|Q+=N0pr1?-89$pbn7|?->)QOI7G*q-WNPsbXj*`B1H<30 zJMH$d3S_%|Lx*1=k`L!JQD9g2t9}^bG?lAf!*8}4EjO@05mk!I)Rl4VRB`R= zxLz&Cb%F6G3`Mqv#w5ST=?5yL$q1Er)Ger{Kr&NQ8(UPRlj%&Zqg0mc%q|_B&wc#0 z0LKq4bC|!U(S3>h%D`xF4iCe_!-KG4ZD*j8j*`=|aGwKErYwr?%P3m8EPkA6Vaq@V zBw~3gdZi}I4}r#ubp5M2&YRt{uWzEBpxu6$+`hTIyEIBL@8?_^)+e$wa9N7SNjgD8 z5Hd7LQZz41LZ7un%I<2I7d;IPSG2+*BVpwV$2;|EC#Wby?NQ z%V}rzsb84!n{$9)81(B#c-d2Re>(-loVV&kcQg4k!i)L@ktsxS?W&SB?Q;_blC0$sMXNI!*RqL{9~p7YTUS$<6(l2Y ztr4(Yk|8IOq<7EFqko@%JUX_Ve3GB*Tz$gXN|f8}i$V8&`ORRyhPYsU3L}&yOElDu z)6X)RM{ciVG<-tm9db_DwBTY^6CtwhxcuKjLBgt*v-pXyl1gqwdRPuOzVPAq`xdag zg*)zTuRFHEdY<%blhU-@hDCE9QJNLMp*CP8+nR1WJv#(;2<#BpA+QR8@01i0)c_a( E0JZ5Y`Tzg`