r100337 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r100336‎ | r100337 | r100338 >
Date:11:02, 20 October 2011
Author:questpc
Status:deferred
Tags:
Comment:
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 (modified) (history)
  • /trunk/extensions/QPoll/clientside/qp_user.css (modified) (history)
  • /trunk/extensions/QPoll/clientside/qp_user.js (modified) (history)
  • /trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php (modified) (history)
  • /trunk/extensions/QPoll/ctrl/question/qp_textquestion.php (modified) (history)
  • /trunk/extensions/QPoll/includes/qp_renderer.php (modified) (history)
  • /trunk/extensions/QPoll/view/question/qp_textquestionview.php (modified) (history)
  • /trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php (modified) (history)

Diff [purge]

Index: trunk/extensions/QPoll/clientside/qp_user.css
@@ -1,58 +1,58 @@
22 .qpoll .error { background-color: LightYellow; }
33 .qpoll .object_error { background-color: #D700D7; }
4 -.qpoll .fatalerror { border: 1px solid gray; padding: 4px; background-color: LightYellow; }
 4+.qpoll .fatalerror { border: 1px solid Gray; padding: 4px; background-color: LightYellow; }
55 .qpoll .settings input.numerical { width:2em; }
66 .qpoll .header {font-weight: bold;}
77 *>.qpoll .header .questionId {text-indent: -1.5em; } /* *> prevent ie6 to interprate it. */
88 .qpoll table.object { background-color:transparent; border-collapse:collapse; }
99 .qpoll table.object .proposaltext { }
1010 .qpoll table.object th { padding-left: 5px; padding-right: 5px; vertical-align:bottom; }
11 -.qpoll table.object .spans { color:gray; }
 11+.qpoll table.object .spans { color:Gray; }
1212 .qpoll table.object .categories { color:#444455; }
1313 .qpoll table.object .proposal { }
14 -.qpoll span.proposalerror { color:red; font-weight:600; }
 14+.qpoll span.proposalerror { color:Red; font-weight:600; }
1515 .qpoll tr.proposalerror { background-color: Snow; }
1616 .qpoll .settings td { padding: 0.1em 0.4em 0.1em 0.4em }
1717 .qpoll table.settings { background-color:transparent; }
1818 /* Part for the basic types's inputs. */
1919 .qpoll table.stats td { text-align:center; margin:2px; padding:0px; border:none; border-collapse:collapse; }
20 -.qpoll div.stats { text-align:center; font-size:7pt; color:green; padding-left: 2px; padding-right: 2px;}
21 -.qpoll div.stats1 { text-align:center; font-size:7pt; color:green; width:162px; margin:0px auto; margin-top:2px; }
22 -.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; }
 20+.qpoll div.stats { text-align:center; font-size:7pt; color:Green; padding-left: 2px; padding-right: 2px;}
 21+.qpoll div.stats1 { text-align:center; font-size:7pt; color:Green; width:162px; margin:0px auto; margin-top:2px; }
 22+.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; }
2323 .qpoll div.stats2 div { height:20px; }
2424 .qpoll div.bar0 { float:left; width:30px; }
25 -.qpoll div.bar1 { float:left; background-color:gainsboro; border-left: 1px solid gray; border-top: 1px solid gray; border-bottom: 1px solid gray; }
26 -.qpoll div.bar2 { float:left; background-color:linen; border-right: 1px solid gray; border-top: 1px solid gray; border-bottom: 1px solid gray; }
 25+.qpoll div.bar1 { float:left; background-color:Gainsboro; border-left: 1px solid Gray; border-top: 1px solid Gray; border-bottom: 1px solid Gray; }
 26+.qpoll div.bar2 { float:left; background-color:Linen; border-right: 1px solid Gray; border-top: 1px solid Gray; border-bottom: 1px solid Gray; }
2727 .qpoll div.bar3 { width:100px; left:0px; top:-20px; position:relative; z-index:10; }
2828 .qpoll .sign { text-align:center; }
29 -.qpoll .signl { text-align:center; border-bottom:1px solid gray; border-top: 1px solid gray; border-left: 1px solid gray; }
30 -.qpoll .signc { text-align:center; border-bottom:1px solid gray; border-top: 1px solid gray; }
31 -.qpoll .signr { text-align:center; border-bottom:1px solid gray; border-top: 1px solid gray; border-right: 1px solid gray; }
32 -.qpoll .signt { text-align:center; border-left: 1px solid gray; border-right: 1px solid gray; border-top: 1px solid gray; }
33 -.qpoll .signm { text-align:center; border-left: 1px solid gray; border-right: 1px solid gray; }
34 -.qpoll .signb { text-align:center; border-left: 1px solid gray; border-right: 1px solid gray; border-bottom:1px solid gray; }
 29+.qpoll .signl { text-align:center; border-bottom:1px solid Gray; border-top: 1px solid Gray; border-left: 1px solid Gray; }
 30+.qpoll .signc { text-align:center; border-bottom:1px solid Gray; border-top: 1px solid Gray; }
 31+.qpoll .signr { text-align:center; border-bottom:1px solid Gray; border-top: 1px solid Gray; border-right: 1px solid Gray; }
 32+.qpoll .signt { text-align:center; border-left: 1px solid Gray; border-right: 1px solid Gray; border-top: 1px solid Gray; }
 33+.qpoll .signm { text-align:center; border-left: 1px solid Gray; border-right: 1px solid Gray; }
 34+.qpoll .signb { text-align:center; border-left: 1px solid Gray; border-right: 1px solid Gray; border-bottom:1px solid Gray; }
3535 /* Part for the inputfields */
36 -.qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll a.input:visited { text-decoration:none; color:black; outline: 0 }
 36+.qpoll a.input, .qpoll a.input:hover, .qpoll a.input:active, .qpoll a.input:visited { text-decoration:none; color:Black; outline: 0 }
3737 .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 */
38 -.qpoll .cat_prefilled { color: blue; }
 38+.qpoll .cat_prefilled { color: Blue; }
3939 .qpoll .cat_noanswer { background-color: LightYellow; }
40 -.qpoll .cat_error { background-color: LightYellow; border: 1px solid gray; }
41 -.qpoll .interp_error { border: 2px solid gray; padding: 0.5em; color: white; background-color: red; margin: 0.3em 0 0.3em 0; }
42 -.qpoll .interp_answer { border: 2px solid gray; padding: 0.5em; color: black; background-color: lightblue; margin: 0.3em 0 0.3em 0; }
 40+.qpoll .cat_error { background-color: LightYellow; border: 1px solid Gray; }
 41+.qpoll .interp_error { border: 2px solid Gray; padding: 0.5em; color: White; background-color: Red; margin: 0.3em 0 0.3em 0; }
 42+.qpoll .interp_answer { border: 2px solid Gray; padding: 0.5em; color: Black; background-color: LightBlue; margin: 0.3em 0 0.3em 0; }
4343 .qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 0.3em 0; }
44 -.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; }
45 -.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; }
 44+.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; }
 45+.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; }
4646
4747 /* script view */
48 -.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; }
49 -.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; }
 48+.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; }
 49+.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; }
5050
5151 /* LTR part (RTL is included from separate file) */
52 -.qpoll .attempts_counter{ border: 1px solid gray; padding: 0.1em 0.5em 0.1em 0.5em; color: black; background-color: lightblue; margin-left: 1em; }
 52+.qpoll .attempts_counter{ border: 1px solid gray; padding: 0.1em 0.5em 0.1em 0.5em; color: Black; background-color: LightBlue; margin-left: 1em; }
5353 .qpoll .question { margin-left:2em; padding:0.3em; }
5454 .qpoll .margin { padding-left:20px; }
5555 .qpoll .header .questionId { font-size: 1.1em; font-weight: bold; float: left; }
5656 .qpoll .header .questionId { *padding-right: 0.5em; } /* applies to IE7 and below */
57 -.qpoll a.input em { color:black; background-color:#DFDFDF; margin-right:1px; }
 57+.qpoll a.input em { color:Black; background-color:#DFDFDF; margin-right:1px; }
5858 .qpoll a.input input { padding-left:2px; border:0; }
5959 .qpoll .error_mark { border-left: 3px solid #D700D7; }
Index: trunk/extensions/QPoll/clientside/qp_results.css
@@ -1,17 +1,18 @@
2 -.qpoll .head {font-weight:bold; color:gray;}
 2+.qpoll .head {font-weight:bold; color:Gray;}
33 .qpoll table.qdata {border-collapse: collapse;}
4 -.qpoll table.qdata th {border: 1px gray solid; background-color:lightgray; padding: 3px;}
 4+.qpoll table.qdata th {border: 1px Gray solid; background-color:LightGrey; padding: 3px;}
55 .qpoll table.qdata tr.spans { color:Navy; }
6 -.qpoll table.qdata td {border: 1px gray solid; text-align: center; padding: 3px;}
 6+.qpoll table.qdata td {border: 1px Gray solid; text-align: center; padding: 3px;}
77 .qpoll table.qdata td.stats {background-color: Azure;}
88 .qpoll table.qdata td.spaneven {background-color: Aquamarine;}
99 .qpoll table.qdata td.spanodd {background-color: Moccasin;}
1010 .qpoll table.qdata tr.qdatatext td { text-align: left; }
11 -.qpoll .cat_part { background-color: Lightyellow; border: 1px solid gray; padding: 0 0.5em 0 0.5em; }
12 -.qpoll .cat_unknown { color: white; background-color: IndianRed; border: 1px solid gray; padding: 0 0.5em 0 0.5em; }
13 -.qpoll .interp_header { background-color: lightgray; padding: 0.2em; border: 1px solid gray; margin: 0.3em 0 0.3em 0; }
14 -.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; }
15 -.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; }
 11+.qpoll .cat_part { background-color: LightYellow; border: 1px solid Gray; padding: 0 0.5em 0 0.5em; }
 12+.qpoll .cat_unanswered { color: White; background-color: PowderBlue; border: 1px solid Gray; padding: 0 0.5em 0 0.5em; }
 13+.qpoll .cat_unknown { color: White; background-color: IndianRed; border: 1px solid Gray; padding: 0 0.5em 0 0.5em; }
 14+.qpoll .interp_header { background-color: LightGrey; padding: 0.2em; border: 1px solid Gray; margin: 0.3em 0 0.3em 0; }
 15+.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; }
 16+.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; }
1617 .qpoll table.structured_answer { border-collapse: collapse; margin: 0.3em 0 0.3em 0; }
17 -.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; }
18 -.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; }
 18+.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; }
 19+.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; }
Index: trunk/extensions/QPoll/clientside/qp_user.js
@@ -33,107 +33,203 @@
3434 */
3535
3636 (function() {
37 - function uniqueRadioButtonColumn() {
38 - // example of input id: 'uq1c2p2'
39 - var propId, inp;
40 - var id = new String( this.getAttribute('id') );
41 - // IE split is really buggy, so we have to search for p letter instead, was:
42 - // var params = id.split('/(uq\d{1,}?c\d{1,}?p)(\d{1,}?)/g');
43 - // if ( params !== null && params[1] !== null && params[2] != null ) {
44 - // var currPropId = parseInt( params[2] );
45 - // var basepart = params[1];
46 - // ...
47 - var p = id.indexOf( 'p' ) + 1;
48 - var currPropId = parseInt( id.substring( p ) );
49 - var basepart = id.substring( 0, p );
50 - if ( p != 0) {
51 - for ( propId = 0; ( inp = document.getElementById( basepart + propId ) ) !== null; propId++ ) {
52 - if ( propId != currPropId) {
 37+
 38+ var self = {
 39+
 40+ radioIsClicked : false,
 41+
 42+ // coordinate of current poll's question input id
 43+ catCoord : {
 44+ 'toq' : '', // question prefix which contains "T"ype_of_poll + poll_"O"rder_id + "Q"uestion_id,
 45+ 'p' : '-1', // "P"roposal_id,
 46+ 'c' : '-1' // "C"ategory_id
 47+ },
 48+
 49+ /**
 50+ * Parses coordinate of poll's input stored in id and
 51+ * stores it into self.catCoord;
 52+ * @param id id attribute of input / select element
 53+ * @return true, when value of id has valid coordinate, false otherwise.
 54+ */
 55+ setCatCoord : function( id ) {
 56+ // IE split is really buggy, so we have to search for p and c letters instead
 57+ var p, c;
 58+ if ( ( p = id.indexOf( 'p' ) + 1 ) == -1 ||
 59+ ( c = id.indexOf( 'c' ) + 1 ) == -1 ) {
 60+ return false;
 61+ }
 62+ self.catCoord.toq = id.slice( 0, p - 1 );
 63+ if ( p > c ||
 64+ ( p = parseInt( id.substring( p ) ) ) === NaN ||
 65+ ( c = parseInt( id.substring( c ) ) ) === NaN ) {
 66+ self.catCoord.toq = '';
 67+ return false;
 68+ }
 69+ self.catCoord.p = p;
 70+ self.catCoord.c = c;
 71+ return true;
 72+ },
 73+
 74+ /**
 75+ * Get input node of current poll's question (stored in self.catCoord.toq) with specific coordinates.
 76+ * @param params object 'prop' (optional) property id; 'cat' (optional) category id.
 77+ */
 78+ getCatByCoord : function( params ) {
 79+ var p = ( typeof params['prop'] !== 'undefined' ) ? params['prop'] : self.catCoord.p;
 80+ var c = ( typeof params['cat'] !== 'undefined' ) ? params['cat'] : self.catCoord.c;
 81+ return document.getElementById( self.catCoord.toq + 'p' + p + 'c' + c );
 82+ },
 83+
 84+ /**
 85+ * Logic of switching on / off radiobuttons for all inputs in one proposal line.
 86+ * @param catElem DOM node of poll/question/proposal/category; input or select only.
 87+ */
 88+ applyRadio : function( catElem ) {
 89+ if ( self.radioIsClicked ) {
 90+ // deselect all inputs
 91+ if ( catElem.nodeName == 'SELECT' || catElem.type == 'text' ) {
 92+ catElem.value = '';
 93+ } else {
 94+ catElem.checked = false;
 95+ }
 96+ } else {
 97+ // deselect only radio
 98+ if ( catElem.nodeName == 'INPUT' && catElem.type == 'radio' ) {
 99+ catElem.checked = false;
 100+ }
 101+ }
 102+ },
 103+
 104+ /**
 105+ * Makes this radio button to be unique in their column
 106+ * (it is already unique in their row)
 107+ * Used for question type="unique()"
 108+ */
 109+ uniqueRadioButtonColumn : function() {
 110+ // example of input id: 'uq3q1p4c2'
 111+ // where uq3 is mOrderId of poll on the page
 112+ // q1 first question of that poll
 113+ // p4 proposal index 4 of that question
 114+ // c2 category index 2 of that proposal
 115+ var propId, inp;
 116+ if ( !self.setCatCoord( this.getAttribute( 'id' ) ) ) {
 117+ return;
 118+ }
 119+ for ( propId = 0; ( inp = self.getCatByCoord( { 'prop' : propId } ) ) !== null; propId++ ) {
 120+ if ( propId != self.catCoord.p ) {
53121 inp.checked = false;
54122 }
55123 }
56 - }
57 - }
 124+ },
58125
59 - function clickMixedRow() {
60 - // example of input id: 'mx1p2c2'
61 - var catId, inp;
62 - var id = new String( this.getAttribute('id') );
63 - var c = id.indexOf( 'c' ) + 1;
64 - var currCatId = parseInt( id.substring( c ) );
65 - var basepart = id.substring( 0, c );
66 - if ( c != 0) {
67 - for ( catId = 0; ( inp = document.getElementById( basepart + catId ) ) !== null; catId++ ) {
68 - if ( catId != currCatId ) {
69 - if ( this.type == 'radio' ) {
70 - if (inp.type == 'text' ) {
71 - inp.value = '';
72 - } else {
73 - inp.checked = false;
74 - }
75 - } else {
76 - if (inp.type == 'radio' ) {
77 - inp.checked = false;
78 - }
79 - }
 126+ processMixedRow : function( currElem ) {
 127+ var catId, inp;
 128+ self.radioIsClicked = ( currElem.nodeName == 'INPUT' && currElem.type == 'radio' );
 129+ if ( !self.setCatCoord( currElem.getAttribute( 'id' ) ) ) {
 130+ return false;
 131+ }
 132+ for ( catId = 0; ( inp = self.getCatByCoord( { 'cat' : catId } ) ) !== null; catId++ ) {
 133+ if ( catId != self.catCoord.c ) {
 134+ self.applyRadio( inp );
80135 }
81136 }
82 - }
83 - }
 137+ return true;
 138+ },
84139
85 - function clickPrefilledText() {
86 - if ( this.className === 'cat_prefilled' ) {
87 - this.select();
88 - this.className = 'cat_part';
89 - }
90 - }
 140+ /**
 141+ * Makes this input to switch radiobuttons in the same row
 142+ * Used for questions type="mixed"
 143+ */
 144+ clickMixedRow : function() {
 145+ // example of input id: 'mx1q3p2c4'
 146+ self.processMixedRow( this );
 147+ },
91148
92 - /**
93 - * Prepare the Poll for "javascriptable" browsers
94 - */
95 - function preparePoll() {
96 - var bodyContentDiv = document.getElementById( 'bodyContent' ).getElementsByTagName( 'div' );
97 - var inputFuncId;
98 - for ( var i=0; i<bodyContentDiv.length; ++i ) {
99 - if ( bodyContentDiv[i].className == 'qpoll' ) {
 149+ /**
 150+ * Used for questions type="text", type="text!"
 151+ */
 152+ clickTextRow : function() {
 153+ // example of input id: 'tx1q3p2c4'
 154+ if ( !self.processMixedRow( this ) ) {
 155+ return;
 156+ }
 157+ // clear pre_filled text attribute, when available
 158+ if ( hasClass( this, 'cat_prefilled' ) ) {
 159+ this.select();
 160+ removeClass( this, 'cat_prefilled' );
 161+ }
 162+ },
 163+
 164+ /**
 165+ * Prepare the Poll for "javascriptable" browsers
 166+ */
 167+ preparePoll : function() {
 168+ var bodyContentDiv = document.getElementById( 'bodyContent' ).getElementsByTagName( 'div' );
 169+ var i, j;
 170+ for ( i=0; i<bodyContentDiv.length; i++ ) {
 171+ if ( !hasClass( bodyContentDiv[i], 'qpoll' ) ) {
 172+ continue;
 173+ }
100174 var input = bodyContentDiv[i].getElementsByTagName( 'input' );
101 - for ( var j=0; j<input.length; ++j ) {
 175+ for ( j=0; j<input.length; j++ ) {
102176 if ( input[j].id ) {
103177 // only unique or mixed questions currently have id
104 - inputFuncId = new String( input[j].id ).slice( 0, 2 );
105 - switch ( inputFuncId ) {
106 - case "uq":
 178+ switch ( input[j].id.slice( 0, 2 ) ) {
 179+ case 'uq' :
107180 if ( input[j].type == "radio" ) {
108181 // unset the column of radiobuttons in case of unique proposal
109 - addEvent( input[j], "click", uniqueRadioButtonColumn );
 182+ addEvent( input[j], "click", self.uniqueRadioButtonColumn );
110183 }
111184 break;
112 - case "mx":
 185+ case 'mx' :
113186 // unset the row of checkboxes in case of "mixed" question type
114 - addEvent( input[j], "click", clickMixedRow );
 187+ addEvent( input[j], "click", self.clickMixedRow );
115188 break;
 189+ case 'tx' :
 190+ // handler for text question proposal rows
 191+ addEvent( input[j], "click", self.clickTextRow );
116192 }
117193 } else {
118 - // check non-unique, non-mixed tabular questions and
119 - // text questions
120 - switch ( input[j].getAttribute( 'type' ) ) {
121 - case 'radio' :
 194+ // non-unique, non-mixed tabular questions
 195+ if ( input[j].getAttribute( 'type' ) == 'radio' ) {
122196 // Add the possibility of unchecking radio buttons
123197 addEvent( input[j], "dblclick", function() { this.checked = false; } );
124 - break;
125 - case 'text' :
126 - if ( input[j].className === 'cat_prefilled' ) {
127 - // Add the handler to clear prefilled text
128 - addEvent( input[j], "click", clickPrefilledText );
129 - }
130 - break;
131198 }
132199 }
133200 }
 201+ var select = bodyContentDiv[i].getElementsByTagName( 'select' );
 202+ for ( j = 0; j < select.length; j++ ) {
 203+ // selects currently are used only with question type="text", type="text!"
 204+ if ( select[j].id && select[j].id.slice( 0, 2 ) == 'tx' ) {
 205+ addEvent( select[j], "click", self.clickTextRow );
 206+ }
 207+ }
134208 }
135209 }
 210+ };
 211+
 212+ /**
 213+ * The following functions are defined to not to be dependant on jQuery (MW 1.15).
 214+ * They should not cause any trouble, running in the closure.
 215+ * Class manipulation is taken from http://www.openjs.com/scripts/dom/class_manipulation.php
 216+ */
 217+ function hasClass( ele, cls ) {
 218+ return ele.className.match( new RegExp('(\\s|^)'+cls+'(\\s|$)') );
136219 }
137220
 221+ function addClass( ele, cls ) {
 222+ if ( !this.hasClass(ele,cls)) {
 223+ ele.className += " "+cls;
 224+ }
 225+ }
 226+
 227+ function removeClass( ele, cls ) {
 228+ if ( hasClass(ele,cls) ) {
 229+ var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
 230+ ele.className = ele.className.replace(reg,' ').replace(/\s+/g,' ').replace(/^\s|\s$/,'');
 231+ }
 232+ }
 233+
138234 function addEvent( obj, type, fn ) {
139235 if (obj.addEventListener) {
140236 obj.addEventListener( type, fn, false );
@@ -148,7 +244,7 @@
149245 }
150246 }
151247
152 - if (document.getElementById && document.createTextNode) {
153 - addEvent(window,"load",preparePoll);
 248+ if ( document.getElementById && document.createTextNode ) {
 249+ addEvent( window, "load", self.preparePoll );
154250 }
155 -})();
\ No newline at end of file
 251+})();
Index: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
@@ -159,7 +159,7 @@
160160 class qp_TextQuestion extends qp_StubQuestion {
161161
162162 # regexp for separation of proposal line tokens
163 - static $propCatPattern = null;
 163+ var $propCatPattern = null;
164164
165165 # source "raw" tokens (preg_split)
166166 var $rawtokens;
@@ -167,14 +167,14 @@
168168 /**
169169 * array with parsed braces pairs
170170 * every element may have the following keys:
171 - * 'type' : type of brace string _key_values_ of self::$matching_braces
 171+ * 'type' : type of brace string _key_values_ of $this->matching_braces
172172 * 'closed_at' : indicates opening brace;
173173 * false when no matching closing brace was found
174174 * int key of $this->rawtokens a "link" to matching closing brace
175175 * 'opened_at' : indicates closing brace;
176176 * false when no matching opening brace was found
177177 * int key of $this->rawtokens a "link" to matching opening brace
178 - * 'iscat' : indicates brace that belongs to self::$input_braces_types AND
 178+ * 'iscat' : indicates brace that belongs to $this->input_braces_types AND
179179 * has a proper match (both 'closed_at' and 'opened_at' are int)
180180 */
181181 var $brace_matches;
@@ -190,13 +190,13 @@
191191 var $dbtokens = array();
192192
193193 # list of opening input braces types
194 - static $input_braces_types = array(
 194+ var $input_braces_types = array(
195195 '<<' => 'text',
196196 '<(' => 'radio',
197197 '<[' => 'checkbox'
198198 );
199199 # matches of opening / closing braces
200 - static $matching_braces = array(
 200+ var $matching_braces = array(
201201 # wiki link
202202 '[[' => ']]',
203203 # wiki magicword
@@ -210,23 +210,30 @@
211211 );
212212
213213 /**
214 - * Constructor
215 - * @public
216 - * @param $poll an instance of question's parent controller
217 - * @param $view an instance of question view "linked" to this question
218 - * @param $questionId the identifier of the question used to generate input names
219 - */
220 - function __construct( qp_AbstractPoll $poll, qp_StubQuestionView $view, $questionId ) {
221 - parent::__construct( $poll, $view, $questionId );
222 - if ( self::$propCatPattern === null ) {
 214+ * Applies previousely parsed attributes from main header into question's view
 215+ * (all attributes but type)
 216+ *
 217+ * @param $attr_str - source text with question attributes
 218+ * @return string : type of the question, empty when not defined
 219+ */
 220+ function applyAttributes( $paramkeys ) {
 221+ parent::applyAttributes( $paramkeys );
 222+ if ( $this->mSubType === 'requireAllCategories' ) {
 223+ # radio button prevents from filling all categories, disable it
 224+ if ( ( $radio_brace = array_search( 'radio', $this->input_braces_types, true ) ) !== false ) {
 225+ unset( $this->input_braces_types[$radio_brace] );
 226+ unset( $this->matching_braces[$radio_brace] );
 227+ }
 228+ }
 229+ if ( $this->propCatPattern === null ) {
223230 $braces_list = array_map( 'preg_quote',
224231 array_merge(
225 - ( array_values( self::$matching_braces ) ),
226 - array_keys( self::$matching_braces ),
 232+ ( array_values( $this->matching_braces ) ),
 233+ array_keys( $this->matching_braces ),
227234 array( '|' )
228235 )
229236 );
230 - self::$propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u';
 237+ $this->propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u';
231238 }
232239 }
233240
@@ -241,38 +248,54 @@
242249 }
243250
244251 /**
245 - * Load text answer to the selected (proposal,category) pair, when available
246 - * Also, stores text answer into the parsed tokens list (propview)
 252+ * Load checkbox / radio / text answer to the selected (proposal,category) pair, when available
 253+ * Also, stores checkbox / radio / text answer into the parsed tokens list (propview)
247254 */
248255 function loadProposalCategory( qp_TextQuestionOptions $opt, $proposalId, $catId ) {
249256 $name = "q{$this->mQuestionId}p{$proposalId}s{$catId}";
250 - $text_answer = '';
251 - $user_answered = false;
 257+ # default value for unanswered category
 258+ # boolean true "checked" checkbox / radiobutton
 259+ # string - text input / select option value
 260+ $text_answer = false;
252261 # try to load from POST data
253 - if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( $name ) !== null ) {
254 - $text_answer = trim( $this->mRequest->getText( $name ) );
 262+ if ( $this->poll->mBeingCorrected && $this->mRequest->getVal( $name ) !== null ) {
 263+ if ( $opt->type === 'text' ) {
 264+ if ( ( $ta = trim( $this->mRequest->getText( $name ) ) ) != '' ) {
 265+ if ( strlen( $ta ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH ) {
 266+ $text_answer = substr( $ta, 0, qp_Setup::MAX_TEXT_ANSWER_LENGTH );
 267+ } else {
 268+ $text_answer = $ta;
 269+ }
 270+ }
 271+ } else {
 272+ $text_answer = true;
 273+ }
255274 }
256 - if ( strlen( $text_answer ) > qp_Setup::MAX_TEXT_ANSWER_LENGTH ) {
257 - $text_answer = substr( $text_answer, 0, qp_Setup::MAX_TEXT_ANSWER_LENGTH );
258 - }
259 - if ( $text_answer != '' ) {
260 - $user_answered = true;
261 - }
262275 # try to load from pollStore
263276 # pollStore optionally overrides POST data
264 - $prev_text_answer = $this->answerExists( 'text', $proposalId, $catId );
265 - if ( $prev_text_answer !== false ) {
266 - $user_answered = true;
 277+ $prev_text_answer = $this->answerExists( $opt->type, $proposalId, $catId );
 278+ if ( is_string( $prev_text_answer ) ) {
267279 $text_answer = $prev_text_answer;
 280+ } else {
 281+ if ( $prev_text_answer === true ) {
 282+ $text_answer = true;
 283+ }
268284 }
269 - if ( $user_answered ) {
 285+ if ( $text_answer !== false ) {
270286 # add category to the list of user answers for current proposal (row)
271287 $this->mProposalCategoryId[ $proposalId ][] = $catId;
272 - $this->mProposalCategoryText[ $proposalId ][] = $text_answer;
 288+ if ( is_string( $text_answer ) ) {
 289+ $this->mProposalCategoryText[ $proposalId ][] = $text_answer;
 290+ } else {
 291+ $this->mProposalCategoryText[ $proposalId ][] = '';
 292+ if ( $opt->type !== 'text' ) {
 293+ $opt->attributes['checked'] = true;
 294+ }
 295+ }
273296 }
274297 # finally, add new category input options for the view
275298 $opt->closeCategory();
276 - $this->propview->addCatDef( $opt, $name, $text_answer, $this->poll->mBeingCorrected && !$user_answered );
 299+ $this->propview->addCatDef( $opt, $name, $text_answer, $this->poll->mBeingCorrected && $text_answer === false );
277300 }
278301
279302 /**
@@ -285,30 +308,30 @@
286309 $matching_closed_brace = '';
287310 # building $this->brace_matches
288311 foreach ( $this->rawtokens as $tkey => $token ) {
289 - if ( array_key_exists( $token, self::$matching_braces ) ) {
 312+ if ( array_key_exists( $token, $this->matching_braces ) ) {
290313 # opening braces
291314 $this->brace_matches[$tkey] = array(
292315 'closed_at' => false,
293316 'type' => $token
294317 );
295 - $match = self::$matching_braces[$token];
 318+ $match = $this->matching_braces[$token];
296319 # create new brace_stack element:
297320 $last_brace_def = array(
298321 'match' => $match,
299322 'idx' => $tkey
300323 );
301 - if ( array_key_exists( $token, self::$input_braces_types ) &&
 324+ if ( array_key_exists( $token, $this->input_braces_types ) &&
302325 count( $brace_stack ) == 0 ) {
303326 # will try to start category definiton (on closing)
304327 $matching_closed_brace = $match;
305328 }
306329 array_push( $brace_stack, $last_brace_def );
307 - } elseif ( in_array( $token, self::$matching_braces ) ) {
 330+ } elseif ( in_array( $token, $this->matching_braces ) ) {
308331 # closing braces
309332 $this->brace_matches[$tkey] = array(
310333 'opened_at' => false,
311334 # we always put opening brace in 'type'
312 - 'type' => array_search( $token, self::$matching_braces, true )
 335+ 'type' => array_search( $token, $this->matching_braces, true )
313336 );
314337 if ( count( $brace_stack ) > 0 ) {
315338 $last_brace_def = array_pop( $brace_stack );
@@ -322,7 +345,7 @@
323346 $this->brace_matches[$tkey]['opened_at'] = $idx;
324347 $this->brace_matches[$idx]['closed_at'] = $tkey;
325348 if ( count( $brace_stack ) > 0 || $token !== $matching_closed_brace ) {
326 - # brace does not belong to self::$input_braces_types
 349+ # brace does not belong to $this->input_braces_types
327350 continue;
328351 }
329352 # stack level 1 and found a matching_closed_brace;
@@ -336,15 +359,15 @@
337360 }
338361 }
339362 # trying to backtrack non-closed braces only these which belong to
340 - # self::$input_braces_types
 363+ # $this->input_braces_types
341364 $brace_keys = array_keys( $this->brace_matches, true );
342365 for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) {
343366 $brace_match = &$this->brace_matches[$brace_keys[$i]];
344 - # match non-closed brace which belongs to self::$input_braces_types
 367+ # match non-closed brace which belongs to $this->input_braces_types
345368 # (non-closed category definitions)
346369 if ( array_key_exists( 'opened_at', $brace_match ) &&
347370 $brace_match['opened_at'] === false &&
348 - array_key_exists( $brace_match['type'], self::$input_braces_types ) ) {
 371+ array_key_exists( $brace_match['type'], $this->input_braces_types ) ) {
349372 # try to find matching opening brace for current non-closed closing brace
350373 for ( $j = $i - 1; $j >= 0; $j-- ) {
351374 $checked_brace = &$this->brace_matches[$brace_keys[$j]];
@@ -398,7 +421,7 @@
399422 $this->dbtokens = $brace_stack = array();
400423 $catId = 0;
401424 $last_brace = '';
402 - $this->rawtokens = preg_split( self::$propCatPattern, $raw, -1, PREG_SPLIT_DELIM_CAPTURE );
 425+ $this->rawtokens = preg_split( $this->propCatPattern, $raw, -1, PREG_SPLIT_DELIM_CAPTURE );
403426 $matching_closed_brace = '';
404427 $this->findMatchingBraces();
405428 foreach ( $this->rawtokens as $tkey => $token ) {
@@ -421,11 +444,11 @@
422445 if ( array_key_exists( 'closed_at', $brace_match ) &&
423446 $brace_match['closed_at'] !== false ) {
424447 # valid opening brace
425 - array_push( $brace_stack, self::$matching_braces[$token] );
 448+ array_push( $brace_stack, $this->matching_braces[$token] );
426449 if ( array_key_exists( 'iscat', $brace_match ) ) {
427450 # start category definition
428 - $matching_closed_brace = self::$matching_braces[$token];
429 - $opt->startOptionsList( self::$input_braces_types[$token] );
 451+ $matching_closed_brace = $this->matching_braces[$token];
 452+ $opt->startOptionsList( $this->input_braces_types[$token] );
430453 $toBeStored = false;
431454 }
432455 } elseif ( array_key_exists( 'opened_at', $brace_match ) &&
Index: trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php
@@ -102,8 +102,8 @@
103103 }
104104 # always borderless (mixed questions do not have spans)
105105 $pview->setCategorySpan();
106 - # unique (question,proposal,category) "coordinate" for javascript
107 - $inp['id'] = 'mx' . $this->mQuestionId . 'p' . $proposalId . 'c' . $catId;
 106+ # unique (poll,question,proposal,category) "coordinate" for javascript
 107+ $inp['id'] = "mx{$this->poll->mOrderId}q{$this->mQuestionId}p{$proposalId}c{$catId}";
108108 $inp['class'] = 'check';
109109 $inp['type'] = $inputType;
110110 $inp['name'] = $name;
Index: trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php
@@ -329,8 +329,8 @@
330330 }
331331 $pview->setCategorySpan();
332332 if ( $this->mSubType == 'unique' ) {
333 - # unique (question,category,proposal) "coordinate" for javascript
334 - $inp['id'] = 'uq' . $this->mQuestionId . 'c' . $catId . 'p' . $proposalId;
 333+ # unique (orderid,question,proposal,category) "coordinate" for javascript
 334+ $inp['id'] = "uq{$this->poll->mOrderId}q{$this->mQuestionId}p{$proposalId}c{$catId}";
335335 # If type='unique()' question has more proposals than categories, such question is impossible to complete
336336 if ( count( $this->mProposalText ) > count( $this->mCategories ) ) {
337337 # if there was no previous errors, hightlight the whole row
Index: trunk/extensions/QPoll/includes/qp_renderer.php
@@ -132,12 +132,19 @@
133133 $tag['class'] = $className;
134134 return;
135135 }
136 - if ( array_search( $className, explode( ' ', $tag['class'] ) ) === false ) {
 136+ if ( !self::hasClassName( $tag['class'], $className ) ) {
137137 $tag['class'] .= " $className";
138138 }
139139 }
140140
141141 /**
 142+ * Finds whether the class name already exists in class attribute
 143+ */
 144+ static function hasClassName( $classAttr, $className ) {
 145+ return preg_match( '/(\s|^)' . preg_quote( $className ). '(\s|$)/', $classAttr );
 146+ }
 147+
 148+ /**
142149 * Creates one tagarray row of the table
143150 * @param $row a string/number value of cell or
144151 * an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag )
Index: trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php
@@ -30,13 +30,25 @@
3131 $row[] = array( '__tag' => 'span', 'class' => 'prop_part', qp_Setup::entities( $token ) );
3232 } elseif ( is_array( $token ) ) {
3333 # add a category definition with selected text answer (if any)
 34+ $className = 'cat_part';
3435 if ( array_key_exists( $propkey, $ctrl->ProposalCategoryId ) &&
3536 ( $id_key = array_search( $catId, $ctrl->ProposalCategoryId[$propkey] ) ) !== false ) {
36 - $text_answer = $ctrl->ProposalCategoryText[$propkey][$id_key];
 37+ if ( ( $text_answer = $ctrl->ProposalCategoryText[$propkey][$id_key] ) === '' &&
 38+ count( $token ) === 1 ) {
 39+ # indicate selected checkbox / radiobuttn
 40+ $text_answer = '+';
 41+ }
3742 } else {
3843 $text_answer = '';
 44+ $className .= ' cat_unanswered';
3945 }
40 - $className = ( count( $token ) === 1 || in_array( $text_answer, $token ) ) ? 'cat_part' : 'cat_unknown';
 46+ if ( count( $token ) > 1 &&
 47+ $text_answer !== '' &&
 48+ !in_array( $text_answer, $token ) ) {
 49+ if ( !qp_Renderer::hasClassName( $className, 'cat_unknown' ) ) {
 50+ $className .= ' cat_unknown';
 51+ };
 52+ }
4153 $titleAttr = '';
4254 foreach ( $token as &$option ) {
4355 if ( $option !== $text_answer ) {
@@ -46,7 +58,14 @@
4759 $titleAttr .= qp_Setup::entities( $option );
4860 }
4961 }
50 - $row[] = array( '__tag' => 'span', 'class' => $className, 'title'=>$titleAttr, qp_Setup::entities( $text_answer ) );
 62+ if ( $text_answer === '' ) {
 63+ # many browsers trim the spaces between spans when the text node is empty;
 64+ # use non-breaking space to prevent this
 65+ $text_answer = '&#160;';
 66+ } else {
 67+ $text_answer = qp_Setup::entities( $text_answer );
 68+ }
 69+ $row[] = array( '__tag' => 'span', 'class' => $className, 'title'=>$titleAttr, $text_answer );
5170 # move to the next category (if any)
5271 $catId++;
5372 } else {
Index: trunk/extensions/QPoll/view/question/qp_textquestionview.php
@@ -177,23 +177,32 @@
178178 }
179179
180180 /**
181 - * Generates tagarray representation from the list of viewtokens
 181+ * Generates tagarray representation from the list of viewtokens.
 182+ * @param $pkey proposal index (starting from 0):
 183+ * it is required for JS code, because text questions
 184+ * now may optionally have "tabular transposed" layout.
182185 * @param $viewtokens array of viewtokens
183186 * @return tagarray
184187 */
185 - function renderParsedProposal( &$viewtokens ) {
 188+ function renderParsedProposal( $pkey, &$viewtokens ) {
 189+ # proposal prefix for id generation
 190+ $id_prefix = "tx{$this->ctrl->poll->mOrderId}q{$this->ctrl->mQuestionId}p{$pkey}";
186191 $vr = $this->vr;
187192 $vr->reset();
 193+ # category index, starting from 0
 194+ $ckey = 0;
188195 foreach ( $viewtokens as $elem ) {
189196 $vr->cell = array();
190197 if ( is_object( $elem ) ) {
191198 if ( isset( $elem->options ) ) {
192199 $className = 'cat_part';
193200 if ( $this->ctrl->mSubType === 'requireAllCategories' && $elem->unanswered ) {
194 - $className = 'cat_noanswer';
 201+ $className .= ' cat_noanswer';
195202 }
196203 if ( isset( $elem->interpError ) ) {
197 - $className = 'cat_noanswer';
 204+ if ( !qp_Renderer::hasClassName( $className, 'cat_noanswer' ) ) {
 205+ $className .= ' cat_noanswer';
 206+ }
198207 # create view for proposal/category error message
199208 $vr->cell[] = array(
200209 '__tag' => 'span',
@@ -210,16 +219,19 @@
211220 if ( !$elem->unanswered && $elem->value === '' && $elem->options[0] !== '' ) {
212221 # input text pre-fill
213222 $value = $elem->options[0];
214 - $className = 'cat_prefilled';
 223+ $className .= ' cat_prefilled';
215224 }
216225 $input = array(
217226 '__tag' => 'input',
 227+ # unique (orderid,question,proposal,category) "coordinate" for javascript
 228+ 'id' => "{$id_prefix}c{$ckey}",
218229 'class' => $className,
219230 'type' => $elem->type,
220231 'name' => $elem->name,
221232 'value' => qp_Setup::specialchars( $value )
222233 );
223 - if ( $elem->attributes['checked'] !== null ) {
 234+ $ckey++;
 235+ if ( $elem->type !== 'text' && $elem->attributes['checked'] === true ) {
224236 $input['checked'] = 'checked';
225237 }
226238 if ( $this->textInputStyle != '' ) {
@@ -253,10 +265,13 @@
254266 }
255267 $vr->cell[] = array(
256268 '__tag' => 'select',
 269+ # unique (poll,question,proposal,category) "coordinate" for javascript
 270+ 'id' => "{$id_prefix}c{$ckey}",
257271 'class' => $className,
258272 'name' => $elem->name,
259273 $html_options
260274 );
 275+ $ckey++;
261276 $vr->addCell();
262277 } elseif ( isset( $elem->error ) ) {
263278 # create view for proposal/category error message
@@ -305,8 +320,8 @@
306321 }
307322 qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps );
308323 }
309 - foreach ( $this->pviews as &$propview ) {
310 - $prop = $this->renderParsedProposal( $propview->viewtokens );
 324+ foreach ( $this->pviews as $pkey => &$propview ) {
 325+ $prop = $this->renderParsedProposal( $pkey, $propview->viewtokens );
311326 $rowattrs = array( 'class' => $propview->rowClass );
312327 if ( $this->transposed ) {
313328 qp_Renderer::addColumn( $questionTable, $prop, $rowattrs );

Status & tagging log