http://www.mediawiki.org/wiki/Special:Code/MediaWiki/100337

Revision: 100337
Author:   questpc
Date:     2011-10-20 11:02:33 +0000 (Thu, 20 Oct 2011)
Log Message:
-----------
Added client side radiobutton switch logic for question type text. Fixes for 
checkboxes and radiobuttons in question type text controller / view pair. 
Capitalized CSS X11 color names.

Modified Paths:
--------------
    trunk/extensions/QPoll/clientside/qp_results.css
    trunk/extensions/QPoll/clientside/qp_user.css
    trunk/extensions/QPoll/clientside/qp_user.js
    trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php
    trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php
    trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
    trunk/extensions/QPoll/includes/qp_renderer.php
    trunk/extensions/QPoll/view/question/qp_textquestionview.php
    trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php

Modified: trunk/extensions/QPoll/clientside/qp_results.css
===================================================================
--- trunk/extensions/QPoll/clientside/qp_results.css    2011-10-20 10:29:51 UTC 
(rev 100336)
+++ trunk/extensions/QPoll/clientside/qp_results.css    2011-10-20 11:02:33 UTC 
(rev 100337)
@@ -1,17 +1,18 @@
-.qpoll .head {font-weight:bold; color:gray;}
+.qpoll .head {font-weight:bold; color:Gray;}
 .qpoll table.qdata {border-collapse: collapse;}
-.qpoll table.qdata th {border: 1px gray solid; background-color:lightgray; 
padding: 3px;}
+.qpoll table.qdata th {border: 1px Gray solid; background-color:LightGrey; 
padding: 3px;}
 .qpoll table.qdata tr.spans { color:Navy; }
-.qpoll table.qdata td {border: 1px gray solid; text-align: center; padding: 
3px;}
+.qpoll table.qdata td {border: 1px Gray solid; text-align: center; padding: 
3px;}
 .qpoll table.qdata td.stats {background-color: Azure;}
 .qpoll table.qdata td.spaneven {background-color: Aquamarine;}
 .qpoll table.qdata td.spanodd {background-color: Moccasin;}
 .qpoll table.qdata tr.qdatatext td { text-align: left; }
-.qpoll .cat_part { background-color: Lightyellow; border: 1px solid gray; 
padding: 0 0.5em 0 0.5em; }
-.qpoll .cat_unknown { color: white; background-color: IndianRed; border: 1px 
solid gray; padding: 0 0.5em 0 0.5em; }
-.qpoll .interp_header { background-color: lightgray; padding: 0.2em; border: 
1px solid gray; margin: 0.3em 0 0.3em 0; }
-.qpoll .interp_answer { border: 1px solid gray; padding: 0.2em; margin: 0.3em 
0 0.3em 0; color: black; background-color: lightblue; font-weight:600; 
line-height:2em; }
-.qpoll .interp_answer_body { border: none; border-top: 1px solid gray; 
padding: 0.5em; margin-top: 0; color: black; background-color: Azure; 
font-weight: normal; line-height:normal; }
+.qpoll .cat_part { background-color: LightYellow; border: 1px solid Gray; 
padding: 0 0.5em 0 0.5em; }
+.qpoll .cat_unanswered { color: White; background-color: PowderBlue; border: 
1px solid Gray; padding: 0 0.5em 0 0.5em; }
+.qpoll .cat_unknown { color: White; background-color: IndianRed; border: 1px 
solid Gray; padding: 0 0.5em 0 0.5em; }
+.qpoll .interp_header { background-color: LightGrey; padding: 0.2em; border: 
1px solid Gray; margin: 0.3em 0 0.3em 0; }
+.qpoll .interp_answer { border: 1px solid Gray; padding: 0.2em; margin: 0.3em 
0 0.3em 0; color: Black; background-color: LightBlue; font-weight:600; 
line-height:2em; }
+.qpoll .interp_answer_body { border: none; border-top: 1px solid Gray; 
padding: 0.5em; margin-top: 0; color: Black; background-color: Azure; 
font-weight: normal; line-height:normal; }
 .qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 
0.3em 0; }
-.qpoll table.structured_answer th { text-align:left; border-top:1px solid 
gray; border-left:1px solid gray; border-right:1px solid gray; 
background-color: lightgray; padding: 0 0.2em 0 0.2em; }
-.qpoll table.structured_answer td { border-bottom:1px solid gray; 
border-left:1px solid gray; border-right:1px solid gray; background-color: 
White; padding: 0 0.2em 0 0.2em; }
+.qpoll table.structured_answer th { text-align:left; border-top:1px solid 
Gray; border-left:1px solid Gray; border-right:1px solid Gray; 
background-color: LightGrey; padding: 0 0.2em 0 0.2em; }
+.qpoll table.structured_answer td { border-bottom:1px solid Gray; 
border-left:1px solid Gray; border-right:1px solid Gray; background-color: 
White; padding: 0 0.2em 0 0.2em; }

Modified: trunk/extensions/QPoll/clientside/qp_user.css
===================================================================
--- trunk/extensions/QPoll/clientside/qp_user.css       2011-10-20 10:29:51 UTC 
(rev 100336)
+++ trunk/extensions/QPoll/clientside/qp_user.css       2011-10-20 11:02:33 UTC 
(rev 100337)
@@ -1,58 +1,58 @@
 .qpoll .error { background-color: LightYellow; }
 .qpoll .object_error { background-color: #D700D7; }
-.qpoll .fatalerror { border: 1px solid gray; padding: 4px; background-color: 
LightYellow; }
+.qpoll .fatalerror { border: 1px solid Gray; padding: 4px; background-color: 
LightYellow; }
 .qpoll .settings input.numerical { width:2em; }
 .qpoll .header {font-weight: bold;}
 *>.qpoll .header .questionId {text-indent: -1.5em; } /* *> prevent ie6 to 
interprate it. */
 .qpoll table.object { background-color:transparent; border-collapse:collapse; }
 .qpoll table.object .proposaltext { }
 .qpoll table.object th { padding-left: 5px; padding-right: 5px; 
vertical-align:bottom; }
-.qpoll table.object .spans { color:gray; }
+.qpoll table.object .spans { color:Gray; }
 .qpoll table.object .categories { color:#444455; }
 .qpoll table.object .proposal { }
-.qpoll span.proposalerror { color:red; font-weight:600; }
+.qpoll span.proposalerror { color:Red; font-weight:600; }
 .qpoll tr.proposalerror { background-color: Snow; }
 .qpoll .settings td { padding: 0.1em 0.4em 0.1em 0.4em }
 .qpoll table.settings { background-color:transparent; }
 /* Part for the basic types's inputs. */
 .qpoll table.stats td { text-align:center; margin:2px; padding:0px; 
border:none; border-collapse:collapse; }
-.qpoll div.stats { text-align:center; font-size:7pt; color:green; 
padding-left: 2px; padding-right: 2px;}
-.qpoll div.stats1 { text-align:center; font-size:7pt; color:green; 
width:162px; margin:0px auto; margin-top:2px; }
-.qpoll div.stats2 { text-align:center; font-size:7pt; height:20px; 
font-weight:bold; color:green; width:105px; margin:0px auto; padding-left:3px; 
padding-bottom:1px; margin-top:1px; margin-bottom:2px; }
+.qpoll div.stats { text-align:center; font-size:7pt; color:Green; 
padding-left: 2px; padding-right: 2px;}
+.qpoll div.stats1 { text-align:center; font-size:7pt; color:Green; 
width:162px; margin:0px auto; margin-top:2px; }
+.qpoll div.stats2 { text-align:center; font-size:7pt; height:20px; 
font-weight:bold; color:Green; width:105px; margin:0px auto; padding-left:3px; 
padding-bottom:1px; margin-top:1px; margin-bottom:2px; }
 .qpoll div.stats2 div { height:20px; }
 .qpoll div.bar0 { float:left; width:30px; }
-.qpoll div.bar1 { float:left; background-color:gainsboro; border-left: 1px 
solid gray; border-top: 1px solid gray; border-bottom: 1px solid gray; }
-.qpoll div.bar2 { float:left; background-color:linen; border-right: 1px solid 
gray; border-top: 1px solid gray; border-bottom: 1px solid gray; }
+.qpoll div.bar1 { float:left; background-color:Gainsboro; border-left: 1px 
solid Gray; border-top: 1px solid Gray; border-bottom: 1px solid Gray; }
+.qpoll div.bar2 { float:left; background-color:Linen; border-right: 1px solid 
Gray; border-top: 1px solid Gray; border-bottom: 1px solid Gray; }
 .qpoll div.bar3 { width:100px; left:0px; top:-20px; position:relative; 
z-index:10; }
 .qpoll .sign { text-align:center; }
-.qpoll .signl { text-align:center; border-bottom:1px solid gray; border-top: 
1px solid gray; border-left: 1px solid gray; }
-.qpoll .signc { text-align:center; border-bottom:1px solid gray; border-top: 
1px solid gray; }
-.qpoll .signr { text-align:center; border-bottom:1px solid gray; border-top: 
1px solid gray; border-right: 1px solid gray; }
-.qpoll .signt { text-align:center; border-left: 1px solid gray; border-right: 
1px solid gray; border-top: 1px solid gray; }
-.qpoll .signm { text-align:center; border-left: 1px solid gray; border-right: 
1px solid gray; }
-.qpoll .signb { text-align:center; border-left: 1px solid gray; border-right: 
1px solid gray; border-bottom:1px solid gray; }
+.qpoll .signl { text-align:center; border-bottom:1px solid Gray; border-top: 
1px solid Gray; border-left: 1px solid Gray; }
+.qpoll .signc { text-align:center; border-bottom:1px solid Gray; border-top: 
1px solid Gray; }
+.qpoll .signr { text-align:center; border-bottom:1px solid Gray; border-top: 
1px solid Gray; border-right: 1px solid Gray; }
+.qpoll .signt { text-align:center; border-left: 1px solid Gray; border-right: 
1px solid Gray; border-top: 1px solid Gray; }
+.qpoll .signm { text-align:center; border-left: 1px solid Gray; border-right: 
1px solid Gray; }
+.qpoll .signb { text-align:center; border-left: 1px solid Gray; border-right: 
1px solid Gray; border-bottom:1px solid Gray; }
 /* Part for the inputfields */
-.qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll 
a.input:visited { text-decoration:none; color:black; outline: 0 }
+.qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll 
a.input:visited { text-decoration:none; color:Black; outline: 0 }
 .qpoll a.input span { outline:#7F9DB9 solid 1px; border:1px solid #7F9DB9; } 
/* *border is for IE6/7 star is removed due to ff console error */
-.qpoll .cat_prefilled { color: blue; }
+.qpoll .cat_prefilled { color: Blue; }
 .qpoll .cat_noanswer { background-color: LightYellow; }
-.qpoll .cat_error { background-color: LightYellow; border: 1px solid gray; }
-.qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; 
background-color: red; margin: 0.3em 0 0.3em 0; }
-.qpoll .interp_answer { border: 2px solid gray; padding: 0.5em; color: black; 
background-color: lightblue; margin: 0.3em 0 0.3em 0; }
+.qpoll .cat_error { background-color: LightYellow; border: 1px solid Gray; }
+.qpoll .interp_error { border: 2px solid Gray; padding: 0.5em; color: White; 
background-color: Red; margin: 0.3em 0 0.3em 0; }
+.qpoll .interp_answer { border: 2px solid Gray; padding: 0.5em; color: Black; 
background-color: LightBlue; margin: 0.3em 0 0.3em 0; }
 .qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 
0.3em 0; }
-.qpoll table.structured_answer th { text-align:left; border-top:1px solid 
gray; border-left:1px solid gray; border-right:1px solid gray; 
background-color: Moccasin; padding: 0 0.2em 0 0.2em; }
-.qpoll table.structured_answer td { border-bottom:1px solid gray; 
border-left:1px solid gray; border-right:1px solid gray; background-color: 
Azure; padding: 0 0.2em 0 0.2em; }
+.qpoll table.structured_answer th { text-align:left; border-top:1px solid 
Gray; border-left:1px solid Gray; border-right:1px solid Gray; 
background-color: Moccasin; padding: 0 0.2em 0 0.2em; }
+.qpoll table.structured_answer td { border-bottom:1px solid Gray; 
border-left:1px solid Gray; border-right:1px solid Gray; background-color: 
Azure; padding: 0 0.2em 0 0.2em; }
 
 /* script view */
-.qpoll .line_numbers { font-family: monospace, "Courier New"; white-space:pre; 
color:green; background-color: lightgray; float:left; border-left: 1px solid 
darkgray; border-top: 1px solid darkgray; border-bottom: 1px solid darkgray; 
padding: 0.5em; }
-.qpoll .script_view { font-family: monospace, "Courier New"; white-space:pre; 
overflow:auto; color:black; background-color: lightgray; border-right: 1px 
solid darkgray; border-top: 1px solid darkgray; border-bottom: 1px solid 
darkgray; padding: 0.5em; }
+.qpoll .line_numbers { font-family: monospace, "Courier New"; white-space:pre; 
color:Green; background-color: LightGray; float:left; border-left: 1px solid 
DarkGray; border-top: 1px solid DarkGray; border-bottom: 1px solid DarkGray; 
padding: 0.5em; }
+.qpoll .script_view { font-family: monospace, "Courier New"; white-space:pre; 
overflow:auto; color:Black; background-color: LightGray; border-right: 1px 
solid DarkGray; border-top: 1px solid DarkGray; border-bottom: 1px solid 
DarkGray; padding: 0.5em; }
 
 /* LTR part (RTL is included from separate file) */
-.qpoll .attempts_counter{ border: 1px solid gray; padding: 0.1em 0.5em 0.1em 
0.5em; color: black; background-color: lightblue; margin-left: 1em; }
+.qpoll .attempts_counter{ border: 1px solid gray; padding: 0.1em 0.5em 0.1em 
0.5em; color: Black; background-color: LightBlue; margin-left: 1em; }
 .qpoll .question { margin-left:2em; padding:0.3em; }
 .qpoll .margin { padding-left:20px; }
 .qpoll .header .questionId { font-size: 1.1em; font-weight: bold; float: left; 
}
 .qpoll .header .questionId { *padding-right: 0.5em; } /* applies to IE7 and 
below */
-.qpoll a.input em { color:black; background-color:#DFDFDF; margin-right:1px; }
+.qpoll a.input em { color:Black; background-color:#DFDFDF; margin-right:1px; }
 .qpoll a.input input { padding-left:2px; border:0; }
 .qpoll .error_mark { border-left: 3px solid #D700D7; }

Modified: trunk/extensions/QPoll/clientside/qp_user.js
===================================================================
--- trunk/extensions/QPoll/clientside/qp_user.js        2011-10-20 10:29:51 UTC 
(rev 100336)
+++ trunk/extensions/QPoll/clientside/qp_user.js        2011-10-20 11:02:33 UTC 
(rev 100337)
@@ -33,107 +33,203 @@
  */
 
 (function() {
-       function uniqueRadioButtonColumn() {
-               // example of input id: 'uq1c2p2'
-               var propId, inp;
-               var id = new String( this.getAttribute('id') );
-               // IE split is really buggy, so we have to search for p letter 
instead, was:
-               // var params = id.split('/(uq\d{1,}?c\d{1,}?p)(\d{1,}?)/g');
-               // if ( params !== null && params[1] !== null && params[2] != 
null ) {
-               //   var currPropId = parseInt( params[2] );
-               //   var basepart = params[1];
-               //   ...
-               var p = id.indexOf( 'p' ) + 1;
-               var currPropId = parseInt( id.substring( p ) );
-               var basepart = id.substring( 0, p );
-               if ( p != 0)  {
-                       for ( propId = 0; ( inp = document.getElementById( 
basepart + propId ) ) !== null; propId++ ) {
-                               if ( propId != currPropId) {
+
+       var self = {
+
+               radioIsClicked : false,
+
+               // coordinate of current poll's question input id
+               catCoord : {
+                       'toq' : '', // question prefix which contains 
"T"ype_of_poll + poll_"O"rder_id + "Q"uestion_id,
+                       'p' : '-1', // "P"roposal_id,
+                       'c' : '-1'  // "C"ategory_id
+               },
+
+               /**
+                * Parses coordinate of poll's input stored in id and
+                * stores it into self.catCoord;
+                * @param  id  id attribute of input / select element
+                * @return  true, when value of id has valid coordinate, false 
otherwise.
+                */
+               setCatCoord : function( id ) {
+                       // IE split is really buggy, so we have to search for p 
and c letters instead
+                       var p, c;
+                       if ( ( p = id.indexOf( 'p' ) + 1 ) == -1 ||
+                                       ( c = id.indexOf( 'c' ) + 1 ) == -1 ) {
+                               return false;
+                       }
+                       self.catCoord.toq = id.slice( 0, p - 1 );
+                       if ( p > c ||
+                                       ( p = parseInt( id.substring( p ) ) ) 
=== NaN ||
+                                       ( c = parseInt( id.substring( c ) ) ) 
=== NaN ) {
+                               self.catCoord.toq = '';
+                               return false;
+                       }
+                       self.catCoord.p = p;
+                       self.catCoord.c = c;
+                       return true;
+               },
+
+               /**
+                * Get input node of current poll's question (stored in 
self.catCoord.toq) with specific coordinates.
+                * @param  params  object  'prop' (optional) property id; 'cat' 
(optional) category id.
+                */
+               getCatByCoord : function( params ) {
+                       var p = ( typeof params['prop'] !== 'undefined' ) ? 
params['prop'] : self.catCoord.p;
+                       var c = ( typeof params['cat'] !== 'undefined' ) ? 
params['cat'] : self.catCoord.c;
+                       return document.getElementById( self.catCoord.toq + 'p' 
+ p + 'c' + c );
+               },
+
+               /**
+                * Logic of switching on / off radiobuttons for all inputs in 
one proposal line.
+                * @param  catElem  DOM node of 
poll/question/proposal/category; input or select only.
+                */
+               applyRadio : function( catElem ) {
+                       if ( self.radioIsClicked ) {
+                               // deselect all inputs
+                               if ( catElem.nodeName == 'SELECT' || 
catElem.type == 'text' ) {
+                                       catElem.value = '';
+                               } else {
+                                       catElem.checked = false;
+                               }
+                       } else {
+                               // deselect only radio
+                               if ( catElem.nodeName == 'INPUT' && 
catElem.type == 'radio' ) {
+                                       catElem.checked = false;
+                               }
+                       }
+               },
+
+               /**
+                * Makes this radio button to be unique in their column
+                * (it is already unique in their row)
+                * Used for question type="unique()"
+                */
+               uniqueRadioButtonColumn : function() {
+                       // example of input id: 'uq3q1p4c2'
+                       // where uq3 is mOrderId of poll on the page
+                       // q1 first question of that poll
+                       // p4 proposal index 4 of that question
+                       // c2 category index 2 of that proposal
+                       var propId, inp;
+                       if ( !self.setCatCoord( this.getAttribute( 'id' ) ) ) {
+                               return;
+                       }
+                       for ( propId = 0; ( inp = self.getCatByCoord( { 'prop' 
: propId } ) ) !== null; propId++ ) {
+                               if ( propId != self.catCoord.p ) {
                                        inp.checked = false;
                                }
                        }
-               }
-       }
+               },
 
-       function clickMixedRow() {
-               // example of input id: 'mx1p2c2'
-               var catId, inp;
-               var id = new String( this.getAttribute('id') );
-               var c = id.indexOf( 'c' ) + 1;
-               var currCatId = parseInt( id.substring( c ) );
-               var basepart = id.substring( 0, c );
-               if ( c != 0)  {
-                       for ( catId = 0; ( inp = document.getElementById( 
basepart + catId ) ) !== null; catId++ ) {
-                               if ( catId != currCatId ) {
-          if ( this.type == 'radio' ) {
-            if (inp.type == 'text' ) {
-              inp.value = '';
-            } else {
-              inp.checked = false;
-            }
-          } else {
-            if (inp.type == 'radio' ) {
-              inp.checked = false;
-            }
-          }
+               processMixedRow : function( currElem ) {
+                       var catId, inp;
+                       self.radioIsClicked = ( currElem.nodeName == 'INPUT' && 
currElem.type == 'radio' );
+                       if ( !self.setCatCoord( currElem.getAttribute( 'id' ) ) 
) {
+                               return false;
+                       }
+                       for ( catId = 0; ( inp = self.getCatByCoord( { 'cat' : 
catId } ) ) !== null; catId++ ) {
+                               if ( catId != self.catCoord.c ) {
+                                       self.applyRadio( inp );
                                }
                        }
-               }
-       }
+                       return true;
+               },
 
-       function clickPrefilledText() {
-               if ( this.className === 'cat_prefilled' ) {
-                       this.select();
-                       this.className = 'cat_part';
-               }
-       }
+               /**
+                * Makes this input to switch radiobuttons in the same row
+                * Used for questions type="mixed"
+                */
+               clickMixedRow : function() {
+                       // example of input id: 'mx1q3p2c4'
+                       self.processMixedRow( this );
+               },
 
-       /**
-        * Prepare the Poll for "javascriptable" browsers
-        */
-       function preparePoll() {
-               var bodyContentDiv = document.getElementById( 'bodyContent' 
).getElementsByTagName( 'div' );
-               var inputFuncId;
-               for ( var i=0; i<bodyContentDiv.length; ++i ) {
-                       if ( bodyContentDiv[i].className == 'qpoll' ) {
+               /**
+                * Used for questions type="text", type="text!"
+                */
+               clickTextRow : function() {
+                       // example of input id: 'tx1q3p2c4'
+                       if ( !self.processMixedRow( this ) ) {
+                               return;
+                       }
+                       // clear pre_filled text attribute, when available
+                       if ( hasClass( this, 'cat_prefilled' ) ) {
+                               this.select();
+                               removeClass( this, 'cat_prefilled' );
+                       }
+               },
+
+               /**
+                * Prepare the Poll for "javascriptable" browsers
+                */
+               preparePoll : function() {
+                       var bodyContentDiv = document.getElementById( 
'bodyContent' ).getElementsByTagName( 'div' );
+                       var i, j;
+                       for ( i=0; i<bodyContentDiv.length; i++ ) {
+                               if ( !hasClass( bodyContentDiv[i], 'qpoll' ) ) {
+                                       continue;
+                               }
                                var input = 
bodyContentDiv[i].getElementsByTagName( 'input' );
-                               for ( var j=0; j<input.length; ++j ) {
+                               for ( j=0; j<input.length; j++ ) {
                                        if ( input[j].id ) {
                                                // only unique or mixed 
questions currently have id
-                                               inputFuncId = new String( 
input[j].id ).slice( 0, 2 );
-                                               switch ( inputFuncId ) {
-                                               case "uq":
+                                               switch ( input[j].id.slice( 0, 
2 ) ) {
+                                               case 'uq' :
                                                        if ( input[j].type == 
"radio" ) {
                                                                // unset the 
column of radiobuttons in case of unique proposal
-                                                               addEvent( 
input[j], "click", uniqueRadioButtonColumn );
+                                                               addEvent( 
input[j], "click", self.uniqueRadioButtonColumn );
                                                        }
                                                        break;
-                                               case "mx":
+                                               case 'mx' :
                                                        // unset the row of 
checkboxes in case of "mixed" question type
-                                                       addEvent( input[j], 
"click", clickMixedRow );
+                                                       addEvent( input[j], 
"click", self.clickMixedRow );
                                                        break;
+                                               case 'tx' :
+                                                       // handler for text 
question proposal rows
+                                                       addEvent( input[j], 
"click", self.clickTextRow );
                                                }
                                        } else {
-                                               // check non-unique, non-mixed 
tabular questions and
-                                               // text questions
-                                               switch ( input[j].getAttribute( 
'type' ) ) {
-                                               case 'radio' :
+                                               // non-unique, non-mixed 
tabular questions
+                                               if ( input[j].getAttribute( 
'type' ) == 'radio' ) {
                                                        // Add the possibility 
of unchecking radio buttons
                                                        addEvent( input[j], 
"dblclick", function() { this.checked = false; } );
-                                                       break;
-                                               case 'text' :
-                                                       if ( input[j].className 
=== 'cat_prefilled' ) {
-                                                               // Add the 
handler to clear prefilled text
-                                                               addEvent( 
input[j], "click", clickPrefilledText );
-                                                       }
-                                                       break;
                                                }
                                        }
                                }
+                               var select = 
bodyContentDiv[i].getElementsByTagName( 'select' );
+                               for ( j = 0; j < select.length; j++ ) {
+                                       // selects currently are used only with 
question type="text", type="text!"
+                                       if ( select[j].id && 
select[j].id.slice( 0, 2 ) == 'tx' ) {
+                                               addEvent( select[j], "click", 
self.clickTextRow );
+                                       }
+                               }
                        }
                }
+       };
+
+       /**
+        * The following functions are defined to not to be dependant on jQuery 
(MW 1.15).
+        * They should not cause any trouble, running in the closure.
+        * Class manipulation is taken from 
http://www.openjs.com/scripts/dom/class_manipulation.php
+        */
+       function hasClass( ele, cls ) {
+               return ele.className.match( new RegExp('(\\s|^)'+cls+'(\\s|$)') 
);
        }
 
+       function addClass( ele, cls ) {
+               if ( !this.hasClass(ele,cls)) {
+                       ele.className += " "+cls;
+               }
+       }
+
+       function removeClass( ele, cls ) {
+               if ( hasClass(ele,cls) ) {
+                       var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
+                       ele.className = ele.className.replace(reg,' 
').replace(/\s+/g,' ').replace(/^\s|\s$/,'');
+               }
+       }
+
        function addEvent( obj, type, fn ) {
                if (obj.addEventListener) {
                        obj.addEventListener( type, fn, false );
@@ -148,7 +244,7 @@
                }
        }
 
-       if (document.getElementById && document.createTextNode) {
-               addEvent(window,"load",preparePoll);
+       if ( document.getElementById && document.createTextNode ) {
+               addEvent( window, "load", self.preparePoll );
        }
-})();
\ No newline at end of file
+})();

Modified: trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php   2011-10-20 
10:29:51 UTC (rev 100336)
+++ trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php   2011-10-20 
11:02:33 UTC (rev 100337)
@@ -102,8 +102,8 @@
                                }
                                # always borderless (mixed questions do not 
have spans)
                                $pview->setCategorySpan();
-                               # unique (question,proposal,category) 
"coordinate" for javascript
-                               $inp['id'] = 'mx' . $this->mQuestionId . 'p' . 
$proposalId . 'c' . $catId;
+                               # unique (poll,question,proposal,category) 
"coordinate" for javascript
+                               $inp['id'] = 
"mx{$this->poll->mOrderId}q{$this->mQuestionId}p{$proposalId}c{$catId}";
                                $inp['class'] = 'check';
                                $inp['type'] = $inputType;
                                $inp['name'] = $name;

Modified: trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php 2011-10-20 
10:29:51 UTC (rev 100336)
+++ trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php 2011-10-20 
11:02:33 UTC (rev 100337)
@@ -329,8 +329,8 @@
                                }
                                $pview->setCategorySpan();
                                if ( $this->mSubType == 'unique' ) {
-                                       # unique (question,category,proposal) 
"coordinate" for javascript
-                                       $inp['id'] = 'uq' . $this->mQuestionId 
. 'c' . $catId . 'p' . $proposalId;
+                                       # unique 
(orderid,question,proposal,category) "coordinate" for javascript
+                                       $inp['id'] = 
"uq{$this->poll->mOrderId}q{$this->mQuestionId}p{$proposalId}c{$catId}";
                                        # If type='unique()' question has more 
proposals than categories, such question is impossible to complete
                                        if ( count( $this->mProposalText ) > 
count( $this->mCategories ) ) {
                                                # if there was no previous 
errors, hightlight the whole row

Modified: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_textquestion.php    2011-10-20 
10:29:51 UTC (rev 100336)
+++ trunk/extensions/QPoll/ctrl/question/qp_textquestion.php    2011-10-20 
11:02:33 UTC (rev 100337)
@@ -159,7 +159,7 @@
 class qp_TextQuestion extends qp_StubQuestion {
 
        # regexp for separation of proposal line tokens
-       static $propCatPattern = null;
+       var $propCatPattern = null;
 
        # source "raw" tokens (preg_split)
        var $rawtokens;
@@ -167,14 +167,14 @@
        /**
         * array with parsed braces pairs
         * every element may have the following keys:
-        *   'type' : type of brace string _key_values_ of 
self::$matching_braces
+        *   'type' : type of brace string _key_values_ of 
$this->matching_braces
         *   'closed_at' : indicates opening brace;
         *     false when no matching closing brace was found
         *     int  key of $this->rawtokens a "link" to matching closing brace
         *   'opened_at' : indicates closing brace;
         *     false when no matching opening brace was found
         *     int  key of $this->rawtokens a "link" to matching opening brace
-        *   'iscat'     : indicates brace that belongs to 
self::$input_braces_types AND
+        *   'iscat'     : indicates brace that belongs to 
$this->input_braces_types AND
         *                 has a proper match (both 'closed_at' and 'opened_at' 
are int)
         */
        var $brace_matches;
@@ -190,13 +190,13 @@
        var $dbtokens = array();
 
        # list of opening input braces types
-       static $input_braces_types = array(
+       var $input_braces_types = array(
                '<<' => 'text',
                '<(' => 'radio',
                '<[' => 'checkbox'
        );
        # matches of opening / closing braces
-       static $matching_braces = array(
+       var $matching_braces = array(
                # wiki link
                '[[' => ']]',
                # wiki magicword
@@ -210,23 +210,30 @@
        );
 
        /**
-        * Constructor
-        * @public
-        * @param  $poll            an instance of question's parent controller
-        * @param  $view            an instance of question view "linked" to 
this question
-        * @param  $questionId      the identifier of the question used to 
generate input names
-        */
-       function __construct( qp_AbstractPoll $poll, qp_StubQuestionView $view, 
$questionId ) {
-               parent::__construct( $poll, $view, $questionId );
-               if ( self::$propCatPattern === null ) {
+        * Applies previousely parsed attributes from main header into 
question's view
+        * (all attributes but type)
+        *
+        * @param   $attr_str - source text with question attributes
+        * @return  string : type of the question, empty when not defined
+        */ 
+       function applyAttributes( $paramkeys ) {
+               parent::applyAttributes( $paramkeys );
+               if ( $this->mSubType === 'requireAllCategories' ) {
+                       # radio button prevents from filling all categories, 
disable it
+                       if ( ( $radio_brace = array_search( 'radio', 
$this->input_braces_types, true ) ) !== false ) {
+                               unset( $this->input_braces_types[$radio_brace] 
);
+                               unset( $this->matching_braces[$radio_brace] );
+                       }
+               }
+               if ( $this->propCatPattern === null ) {
                        $braces_list = array_map( 'preg_quote',
                                array_merge(
-                                       ( array_values( self::$matching_braces 
) ),
-                                       array_keys( self::$matching_braces ),
+                                       ( array_values( $this->matching_braces 
) ),
+                                       array_keys( $this->matching_braces ),
                                        array( '|' )
                                )
                        );
-                       self::$propCatPattern = '/(' . implode( '|', 
$braces_list ) . ')/u';
+                       $this->propCatPattern = '/(' . implode( '|', 
$braces_list ) . ')/u';
                }
        }
 
@@ -241,38 +248,54 @@
        }
 
        /**
-        * Load text answer to the selected (proposal,category) pair, when 
available
-        * Also, stores text answer into the parsed tokens list (propview)
+        * Load checkbox / radio / text answer to the selected 
(proposal,category) pair, when available
+        * Also, stores checkbox / radio / text answer into the parsed tokens 
list (propview)
         */
        function loadProposalCategory( qp_TextQuestionOptions $opt, 
$proposalId, $catId ) {
                $name = "q{$this->mQuestionId}p{$proposalId}s{$catId}";
-               $text_answer = '';
-               $user_answered = false;
+               # default value for unanswered category
+               # boolean true "checked" checkbox / radiobutton
+               # string - text input / select option value
+               $text_answer = false;
                # try to load from POST data
-               if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( 
$name ) !== null ) { 
-                       $text_answer = trim( $this->mRequest->getText( $name ) 
);
+               if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( 
$name ) !== null ) {
+                       if ( $opt->type === 'text' ) {
+                               if ( ( $ta = trim( $this->mRequest->getText( 
$name ) ) ) != '' ) {
+                                       if ( strlen( $ta ) > 
qp_Setup::MAX_TEXT_ANSWER_LENGTH ) {
+                                               $text_answer = substr( $ta, 0, 
qp_Setup::MAX_TEXT_ANSWER_LENGTH );
+                                       } else {
+                                               $text_answer = $ta;
+                                       }
+                               }
+                       } else {
+                               $text_answer = true;
+                       }
                }
-               if ( strlen( $text_answer ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH 
) {
-                       $text_answer = substr( $text_answer, 0, 
qp_Setup::MAX_TEXT_ANSWER_LENGTH );
-               }
-               if ( $text_answer != '' ) {
-                       $user_answered = true;
-               }
                # try to load from pollStore
                # pollStore optionally overrides POST data
-               $prev_text_answer = $this->answerExists( 'text', $proposalId, 
$catId );
-               if ( $prev_text_answer !== false ) {
-                       $user_answered = true;
+               $prev_text_answer = $this->answerExists( $opt->type, 
$proposalId, $catId );
+               if ( is_string( $prev_text_answer ) ) {
                        $text_answer = $prev_text_answer;
+               } else {
+                       if ( $prev_text_answer === true ) {
+                               $text_answer = true;
+                       }
                }
-               if ( $user_answered ) {
+               if ( $text_answer !== false ) {
                        # add category to the list of user answers for current 
proposal (row)
                        $this->mProposalCategoryId[ $proposalId ][] = $catId;
-                       $this->mProposalCategoryText[ $proposalId ][] = 
$text_answer;
+                       if ( is_string( $text_answer ) ) {
+                               $this->mProposalCategoryText[ $proposalId ][] = 
$text_answer;
+                       } else {
+                               $this->mProposalCategoryText[ $proposalId ][] = 
'';
+                               if ( $opt->type !== 'text' ) {
+                                       $opt->attributes['checked'] = true;
+                               }
+                       }
                }
                # finally, add new category input options for the view
                $opt->closeCategory();
-               $this->propview->addCatDef( $opt, $name, $text_answer, 
$this->poll->mBeingCorrected && !$user_answered );
+               $this->propview->addCatDef( $opt, $name, $text_answer, 
$this->poll->mBeingCorrected && $text_answer === false );
        }
 
        /**
@@ -285,30 +308,30 @@
                $matching_closed_brace = '';
                # building $this->brace_matches
                foreach ( $this->rawtokens as $tkey => $token ) {
-                       if ( array_key_exists( $token, self::$matching_braces ) 
) {
+                       if ( array_key_exists( $token, $this->matching_braces ) 
) {
                                # opening braces
                                $this->brace_matches[$tkey] = array(
                                        'closed_at' => false,
                                        'type' => $token
                                );
-                               $match = self::$matching_braces[$token];
+                               $match = $this->matching_braces[$token];
                                # create new brace_stack element:
                                $last_brace_def = array(
                                        'match' => $match,
                                        'idx' => $tkey
                                );
-                               if ( array_key_exists( $token, 
self::$input_braces_types ) &&
+                               if ( array_key_exists( $token, 
$this->input_braces_types ) &&
                                                count( $brace_stack ) == 0 ) {
                                        # will try to start category definiton 
(on closing)
                                        $matching_closed_brace = $match;
                                }
                                array_push( $brace_stack, $last_brace_def );
-                       } elseif ( in_array( $token, self::$matching_braces ) ) 
{
+                       } elseif ( in_array( $token, $this->matching_braces ) ) 
{
                                # closing braces
                                $this->brace_matches[$tkey] = array(
                                        'opened_at' => false,
                                        # we always put opening brace in 'type'
-                                       'type' => array_search( $token, 
self::$matching_braces, true )
+                                       'type' => array_search( $token, 
$this->matching_braces, true )
                                );
                                if ( count( $brace_stack ) > 0 ) {
                                        $last_brace_def = array_pop( 
$brace_stack );
@@ -322,7 +345,7 @@
                                        
$this->brace_matches[$tkey]['opened_at'] = $idx;
                                        $this->brace_matches[$idx]['closed_at'] 
= $tkey;
                                        if ( count( $brace_stack ) > 0 || 
$token !== $matching_closed_brace ) {
-                                               # brace does not belong to 
self::$input_braces_types
+                                               # brace does not belong to 
$this->input_braces_types
                                                continue;
                                        }
                                        # stack level 1 and found a 
matching_closed_brace;
@@ -336,15 +359,15 @@
                        }
                }
                # trying to backtrack non-closed braces only these which belong 
to
-               # self::$input_braces_types
+               # $this->input_braces_types
                $brace_keys = array_keys( $this->brace_matches, true );
                for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) {
                        $brace_match = &$this->brace_matches[$brace_keys[$i]];
-                       # match non-closed brace which belongs to 
self::$input_braces_types
+                       # match non-closed brace which belongs to 
$this->input_braces_types
                        # (non-closed category definitions)
                        if ( array_key_exists( 'opened_at', $brace_match ) &&
                                        $brace_match['opened_at'] === false &&
-                                       array_key_exists( $brace_match['type'], 
self::$input_braces_types ) ) {
+                                       array_key_exists( $brace_match['type'], 
$this->input_braces_types ) ) {
                                # try to find matching opening brace for 
current non-closed closing brace
                                for ( $j = $i - 1; $j >= 0; $j-- ) {
                                        $checked_brace = 
&$this->brace_matches[$brace_keys[$j]];
@@ -398,7 +421,7 @@
                        $this->dbtokens = $brace_stack = array();
                        $catId = 0;
                        $last_brace = '';
-                       $this->rawtokens = preg_split( self::$propCatPattern, 
$raw, -1, PREG_SPLIT_DELIM_CAPTURE );
+                       $this->rawtokens = preg_split( $this->propCatPattern, 
$raw, -1, PREG_SPLIT_DELIM_CAPTURE );
                        $matching_closed_brace = '';
                        $this->findMatchingBraces();
                        foreach ( $this->rawtokens as $tkey => $token ) {
@@ -421,11 +444,11 @@
                                        if ( array_key_exists( 'closed_at', 
$brace_match ) &&
                                                        
$brace_match['closed_at'] !== false ) {
                                                # valid opening brace
-                                               array_push( $brace_stack, 
self::$matching_braces[$token] );
+                                               array_push( $brace_stack, 
$this->matching_braces[$token] );
                                                if ( array_key_exists( 'iscat', 
$brace_match ) ) {
                                                        # start category 
definition
-                                                       $matching_closed_brace 
= self::$matching_braces[$token];
-                                                       $opt->startOptionsList( 
self::$input_braces_types[$token] );
+                                                       $matching_closed_brace 
= $this->matching_braces[$token];
+                                                       $opt->startOptionsList( 
$this->input_braces_types[$token] );
                                                        $toBeStored = false;
                                                }
                                        } elseif ( array_key_exists( 
'opened_at', $brace_match ) &&

Modified: trunk/extensions/QPoll/includes/qp_renderer.php
===================================================================
--- trunk/extensions/QPoll/includes/qp_renderer.php     2011-10-20 10:29:51 UTC 
(rev 100336)
+++ trunk/extensions/QPoll/includes/qp_renderer.php     2011-10-20 11:02:33 UTC 
(rev 100337)
@@ -132,12 +132,19 @@
                        $tag['class'] = $className;
                        return;
                }
-               if ( array_search( $className, explode( ' ', $tag['class'] ) ) 
=== false ) {
+               if ( !self::hasClassName( $tag['class'], $className ) ) {
                        $tag['class'] .= " $className";
                }
        }
 
        /**
+        * Finds whether the class name already exists in class attribute
+        */
+       static function hasClassName( $classAttr, $className ) {
+               return preg_match( '/(\s|^)' . preg_quote( $className ). 
'(\s|$)/', $classAttr );
+       }
+
+       /**
         * Creates one tagarray row of the table
         * @param  $row  a string/number value of cell or
         *               an array( "count"=>colspannum, "attribute"=>value, 
0=>html_inside_tag )

Modified: trunk/extensions/QPoll/view/question/qp_textquestionview.php
===================================================================
--- trunk/extensions/QPoll/view/question/qp_textquestionview.php        
2011-10-20 10:29:51 UTC (rev 100336)
+++ trunk/extensions/QPoll/view/question/qp_textquestionview.php        
2011-10-20 11:02:33 UTC (rev 100337)
@@ -177,23 +177,32 @@
        }
 
        /**
-        * Generates tagarray representation from the list of viewtokens
+        * Generates tagarray representation from the list of viewtokens.
+        * @param   $pkey  proposal index (starting from 0):
+        *                 it is required for JS code, because text questions
+        *                 now may optionally have "tabular transposed" layout.
         * @param   $viewtokens  array of viewtokens
         * @return  tagarray
         */
-       function renderParsedProposal( &$viewtokens ) {
+       function renderParsedProposal( $pkey, &$viewtokens ) {
+               # proposal prefix for id generation
+               $id_prefix = 
"tx{$this->ctrl->poll->mOrderId}q{$this->ctrl->mQuestionId}p{$pkey}";
                $vr = $this->vr;
                $vr->reset();
+               # category index, starting from 0
+               $ckey = 0;
                foreach ( $viewtokens as $elem ) {
                        $vr->cell = array();
                        if ( is_object( $elem ) ) {
                                if ( isset( $elem->options ) ) {
                                        $className = 'cat_part';
                                        if ( $this->ctrl->mSubType === 
'requireAllCategories' && $elem->unanswered ) {
-                                               $className = 'cat_noanswer';
+                                               $className .= ' cat_noanswer';
                                        }
                                        if ( isset( $elem->interpError ) ) {
-                                               $className = 'cat_noanswer';
+                                               if ( 
!qp_Renderer::hasClassName( $className, 'cat_noanswer' ) ) {
+                                                       $className .= ' 
cat_noanswer';
+                                               }
                                                # create view for 
proposal/category error message
                                                $vr->cell[] = array(
                                                        '__tag' => 'span',
@@ -210,16 +219,19 @@
                                                if ( !$elem->unanswered && 
$elem->value === '' && $elem->options[0] !== '' ) {
                                                        # input text pre-fill
                                                        $value = 
$elem->options[0];
-                                                       $className = 
'cat_prefilled';
+                                                       $className .= ' 
cat_prefilled';
                                                }
                                                $input = array(
                                                        '__tag' => 'input',
+                                                       # unique 
(orderid,question,proposal,category) "coordinate" for javascript
+                                                       'id' => 
"{$id_prefix}c{$ckey}",
                                                        'class' => $className,
                                                        'type' => $elem->type,
                                                        'name' => $elem->name,
                                                        'value' => 
qp_Setup::specialchars( $value )
                                                );
-                                               if ( 
$elem->attributes['checked'] !== null ) {
+                                               $ckey++;
+                                               if ( $elem->type !== 'text' && 
$elem->attributes['checked'] === true ) {
                                                        $input['checked'] = 
'checked';
                                                }
                                                if ( $this->textInputStyle != 
'' ) {
@@ -253,10 +265,13 @@
                                        }
                                        $vr->cell[] = array(
                                                '__tag' => 'select',
+                                               # unique 
(poll,question,proposal,category) "coordinate" for javascript
+                                               'id' => "{$id_prefix}c{$ckey}",
                                                'class' => $className,
                                                'name' => $elem->name,
                                                $html_options
                                        );
+                                       $ckey++;
                                        $vr->addCell();
                                } elseif ( isset( $elem->error ) ) {
                                        # create view for proposal/category 
error message
@@ -305,8 +320,8 @@
                        }
                        qp_Renderer::addRow( $questionTable, $row, $rowattrs, 
'th', $attribute_maps );
                }
-               foreach ( $this->pviews as &$propview ) {
-                       $prop = $this->renderParsedProposal( 
$propview->viewtokens );
+               foreach ( $this->pviews as $pkey => &$propview ) {
+                       $prop = $this->renderParsedProposal( $pkey, 
$propview->viewtokens );
                        $rowattrs = array( 'class' => $propview->rowClass );
                        if ( $this->transposed ) {
                                qp_Renderer::addColumn( $questionTable, $prop, 
$rowattrs );

Modified: trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php
===================================================================
--- trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php  
2011-10-20 10:29:51 UTC (rev 100336)
+++ trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php  
2011-10-20 11:02:33 UTC (rev 100337)
@@ -30,13 +30,25 @@
                                        $row[] = array( '__tag' => 'span', 
'class' => 'prop_part', qp_Setup::entities( $token ) );
                                } elseif ( is_array( $token ) ) {
                                        # add a category definition with 
selected text answer (if any)
+                                       $className = 'cat_part';
                                        if ( array_key_exists( $propkey, 
$ctrl->ProposalCategoryId ) &&
                                                ( $id_key = array_search( 
$catId, $ctrl->ProposalCategoryId[$propkey] ) ) !== false ) {
-                                               $text_answer = 
$ctrl->ProposalCategoryText[$propkey][$id_key];
+                                               if ( ( $text_answer = 
$ctrl->ProposalCategoryText[$propkey][$id_key] ) === '' &&
+                                                                       count( 
$token ) === 1 ) {
+                                                       # indicate selected 
checkbox / radiobuttn
+                                                       $text_answer = '+';
+                                               }
                                        } else {
                                                $text_answer = '';
+                                               $className .= ' cat_unanswered';
                                        }
-                                       $className = ( count( $token ) === 1 || 
in_array( $text_answer, $token ) ) ? 'cat_part' : 'cat_unknown';
+                                       if ( count( $token ) > 1 &&
+                                                       $text_answer !== '' &&
+                                                       !in_array( 
$text_answer, $token ) ) {
+                                               if ( 
!qp_Renderer::hasClassName( $className, 'cat_unknown' ) ) {
+                                                       $className .= ' 
cat_unknown';
+                                               };
+                                       }
                                        $titleAttr = '';
                                        foreach ( $token as &$option ) {
                                                if ( $option !== $text_answer ) 
{
@@ -46,7 +58,14 @@
                                                        $titleAttr .= 
qp_Setup::entities( $option );
                                                }
                                        }
-                                       $row[] = array( '__tag' => 'span', 
'class' => $className, 'title'=>$titleAttr, qp_Setup::entities( $text_answer ) 
);
+                                       if ( $text_answer === '' ) {
+                                               # many browsers trim the spaces 
between spans when the text node is empty;
+                                               # use non-breaking space to 
prevent this
+                                               $text_answer = '&#160;';
+                                       } else {
+                                               $text_answer = 
qp_Setup::entities( $text_answer );
+                                       }
+                                       $row[] = array( '__tag' => 'span', 
'class' => $className, 'title'=>$titleAttr, $text_answer );
                                        # move to the next category (if any)
                                        $catId++;
                                } else {


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to