Index: trunk/extensions/QPoll/clientside/qp_user.css |
— | — | @@ -1,58 +1,58 @@ |
2 | 2 | .qpoll .error { background-color: LightYellow; } |
3 | 3 | .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; } |
5 | 5 | .qpoll .settings input.numerical { width:2em; } |
6 | 6 | .qpoll .header {font-weight: bold;} |
7 | 7 | *>.qpoll .header .questionId {text-indent: -1.5em; } /* *> prevent ie6 to interprate it. */ |
8 | 8 | .qpoll table.object { background-color:transparent; border-collapse:collapse; } |
9 | 9 | .qpoll table.object .proposaltext { } |
10 | 10 | .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; } |
12 | 12 | .qpoll table.object .categories { color:#444455; } |
13 | 13 | .qpoll table.object .proposal { } |
14 | | -.qpoll span.proposalerror { color:red; font-weight:600; } |
| 14 | +.qpoll span.proposalerror { color:Red; font-weight:600; } |
15 | 15 | .qpoll tr.proposalerror { background-color: Snow; } |
16 | 16 | .qpoll .settings td { padding: 0.1em 0.4em 0.1em 0.4em } |
17 | 17 | .qpoll table.settings { background-color:transparent; } |
18 | 18 | /* Part for the basic types's inputs. */ |
19 | 19 | .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; } |
23 | 23 | .qpoll div.stats2 div { height:20px; } |
24 | 24 | .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; } |
27 | 27 | .qpoll div.bar3 { width:100px; left:0px; top:-20px; position:relative; z-index:10; } |
28 | 28 | .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; } |
35 | 35 | /* 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 } |
37 | 37 | .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; } |
39 | 39 | .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; } |
43 | 43 | .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; } |
46 | 46 | |
47 | 47 | /* 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; } |
50 | 50 | |
51 | 51 | /* 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; } |
53 | 53 | .qpoll .question { margin-left:2em; padding:0.3em; } |
54 | 54 | .qpoll .margin { padding-left:20px; } |
55 | 55 | .qpoll .header .questionId { font-size: 1.1em; font-weight: bold; float: left; } |
56 | 56 | .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; } |
58 | 58 | .qpoll a.input input { padding-left:2px; border:0; } |
59 | 59 | .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;} |
3 | 3 | .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;} |
5 | 5 | .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;} |
7 | 7 | .qpoll table.qdata td.stats {background-color: Azure;} |
8 | 8 | .qpoll table.qdata td.spaneven {background-color: Aquamarine;} |
9 | 9 | .qpoll table.qdata td.spanodd {background-color: Moccasin;} |
10 | 10 | .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; } |
16 | 17 | .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 @@ |
34 | 34 | */ |
35 | 35 | |
36 | 36 | (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 ) { |
53 | 121 | inp.checked = false; |
54 | 122 | } |
55 | 123 | } |
56 | | - } |
57 | | - } |
| 124 | + }, |
58 | 125 | |
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 ); |
80 | 135 | } |
81 | 136 | } |
82 | | - } |
83 | | - } |
| 137 | + return true; |
| 138 | + }, |
84 | 139 | |
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 | + }, |
91 | 148 | |
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 | + } |
100 | 174 | var input = bodyContentDiv[i].getElementsByTagName( 'input' ); |
101 | | - for ( var j=0; j<input.length; ++j ) { |
| 175 | + for ( j=0; j<input.length; j++ ) { |
102 | 176 | if ( input[j].id ) { |
103 | 177 | // 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' : |
107 | 180 | if ( input[j].type == "radio" ) { |
108 | 181 | // unset the column of radiobuttons in case of unique proposal |
109 | | - addEvent( input[j], "click", uniqueRadioButtonColumn ); |
| 182 | + addEvent( input[j], "click", self.uniqueRadioButtonColumn ); |
110 | 183 | } |
111 | 184 | break; |
112 | | - case "mx": |
| 185 | + case 'mx' : |
113 | 186 | // unset the row of checkboxes in case of "mixed" question type |
114 | | - addEvent( input[j], "click", clickMixedRow ); |
| 187 | + addEvent( input[j], "click", self.clickMixedRow ); |
115 | 188 | break; |
| 189 | + case 'tx' : |
| 190 | + // handler for text question proposal rows |
| 191 | + addEvent( input[j], "click", self.clickTextRow ); |
116 | 192 | } |
117 | 193 | } 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' ) { |
122 | 196 | // Add the possibility of unchecking radio buttons |
123 | 197 | 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; |
131 | 198 | } |
132 | 199 | } |
133 | 200 | } |
| 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 | + } |
134 | 208 | } |
135 | 209 | } |
| 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|$)') ); |
136 | 219 | } |
137 | 220 | |
| 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 | + |
138 | 234 | function addEvent( obj, type, fn ) { |
139 | 235 | if (obj.addEventListener) { |
140 | 236 | obj.addEventListener( type, fn, false ); |
— | — | @@ -148,7 +244,7 @@ |
149 | 245 | } |
150 | 246 | } |
151 | 247 | |
152 | | - if (document.getElementById && document.createTextNode) { |
153 | | - addEvent(window,"load",preparePoll); |
| 248 | + if ( document.getElementById && document.createTextNode ) { |
| 249 | + addEvent( window, "load", self.preparePoll ); |
154 | 250 | } |
155 | | -})(); |
\ No newline at end of file |
| 251 | +})(); |
Index: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php |
— | — | @@ -159,7 +159,7 @@ |
160 | 160 | class qp_TextQuestion extends qp_StubQuestion { |
161 | 161 | |
162 | 162 | # regexp for separation of proposal line tokens |
163 | | - static $propCatPattern = null; |
| 163 | + var $propCatPattern = null; |
164 | 164 | |
165 | 165 | # source "raw" tokens (preg_split) |
166 | 166 | var $rawtokens; |
— | — | @@ -167,14 +167,14 @@ |
168 | 168 | /** |
169 | 169 | * array with parsed braces pairs |
170 | 170 | * 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 |
172 | 172 | * 'closed_at' : indicates opening brace; |
173 | 173 | * false when no matching closing brace was found |
174 | 174 | * int key of $this->rawtokens a "link" to matching closing brace |
175 | 175 | * 'opened_at' : indicates closing brace; |
176 | 176 | * false when no matching opening brace was found |
177 | 177 | * 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 |
179 | 179 | * has a proper match (both 'closed_at' and 'opened_at' are int) |
180 | 180 | */ |
181 | 181 | var $brace_matches; |
— | — | @@ -190,13 +190,13 @@ |
191 | 191 | var $dbtokens = array(); |
192 | 192 | |
193 | 193 | # list of opening input braces types |
194 | | - static $input_braces_types = array( |
| 194 | + var $input_braces_types = array( |
195 | 195 | '<<' => 'text', |
196 | 196 | '<(' => 'radio', |
197 | 197 | '<[' => 'checkbox' |
198 | 198 | ); |
199 | 199 | # matches of opening / closing braces |
200 | | - static $matching_braces = array( |
| 200 | + var $matching_braces = array( |
201 | 201 | # wiki link |
202 | 202 | '[[' => ']]', |
203 | 203 | # wiki magicword |
— | — | @@ -210,23 +210,30 @@ |
211 | 211 | ); |
212 | 212 | |
213 | 213 | /** |
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 ) { |
223 | 230 | $braces_list = array_map( 'preg_quote', |
224 | 231 | 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 ), |
227 | 234 | array( '|' ) |
228 | 235 | ) |
229 | 236 | ); |
230 | | - self::$propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u'; |
| 237 | + $this->propCatPattern = '/(' . implode( '|', $braces_list ) . ')/u'; |
231 | 238 | } |
232 | 239 | } |
233 | 240 | |
— | — | @@ -241,38 +248,54 @@ |
242 | 249 | } |
243 | 250 | |
244 | 251 | /** |
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) |
247 | 254 | */ |
248 | 255 | function loadProposalCategory( qp_TextQuestionOptions $opt, $proposalId, $catId ) { |
249 | 256 | $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; |
252 | 261 | # 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 | + } |
255 | 274 | } |
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 | | - } |
262 | 275 | # try to load from pollStore |
263 | 276 | # 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 ) ) { |
267 | 279 | $text_answer = $prev_text_answer; |
| 280 | + } else { |
| 281 | + if ( $prev_text_answer === true ) { |
| 282 | + $text_answer = true; |
| 283 | + } |
268 | 284 | } |
269 | | - if ( $user_answered ) { |
| 285 | + if ( $text_answer !== false ) { |
270 | 286 | # add category to the list of user answers for current proposal (row) |
271 | 287 | $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 | + } |
273 | 296 | } |
274 | 297 | # finally, add new category input options for the view |
275 | 298 | $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 ); |
277 | 300 | } |
278 | 301 | |
279 | 302 | /** |
— | — | @@ -285,30 +308,30 @@ |
286 | 309 | $matching_closed_brace = ''; |
287 | 310 | # building $this->brace_matches |
288 | 311 | foreach ( $this->rawtokens as $tkey => $token ) { |
289 | | - if ( array_key_exists( $token, self::$matching_braces ) ) { |
| 312 | + if ( array_key_exists( $token, $this->matching_braces ) ) { |
290 | 313 | # opening braces |
291 | 314 | $this->brace_matches[$tkey] = array( |
292 | 315 | 'closed_at' => false, |
293 | 316 | 'type' => $token |
294 | 317 | ); |
295 | | - $match = self::$matching_braces[$token]; |
| 318 | + $match = $this->matching_braces[$token]; |
296 | 319 | # create new brace_stack element: |
297 | 320 | $last_brace_def = array( |
298 | 321 | 'match' => $match, |
299 | 322 | 'idx' => $tkey |
300 | 323 | ); |
301 | | - if ( array_key_exists( $token, self::$input_braces_types ) && |
| 324 | + if ( array_key_exists( $token, $this->input_braces_types ) && |
302 | 325 | count( $brace_stack ) == 0 ) { |
303 | 326 | # will try to start category definiton (on closing) |
304 | 327 | $matching_closed_brace = $match; |
305 | 328 | } |
306 | 329 | array_push( $brace_stack, $last_brace_def ); |
307 | | - } elseif ( in_array( $token, self::$matching_braces ) ) { |
| 330 | + } elseif ( in_array( $token, $this->matching_braces ) ) { |
308 | 331 | # closing braces |
309 | 332 | $this->brace_matches[$tkey] = array( |
310 | 333 | 'opened_at' => false, |
311 | 334 | # 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 ) |
313 | 336 | ); |
314 | 337 | if ( count( $brace_stack ) > 0 ) { |
315 | 338 | $last_brace_def = array_pop( $brace_stack ); |
— | — | @@ -322,7 +345,7 @@ |
323 | 346 | $this->brace_matches[$tkey]['opened_at'] = $idx; |
324 | 347 | $this->brace_matches[$idx]['closed_at'] = $tkey; |
325 | 348 | 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 |
327 | 350 | continue; |
328 | 351 | } |
329 | 352 | # stack level 1 and found a matching_closed_brace; |
— | — | @@ -336,15 +359,15 @@ |
337 | 360 | } |
338 | 361 | } |
339 | 362 | # trying to backtrack non-closed braces only these which belong to |
340 | | - # self::$input_braces_types |
| 363 | + # $this->input_braces_types |
341 | 364 | $brace_keys = array_keys( $this->brace_matches, true ); |
342 | 365 | for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) { |
343 | 366 | $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 |
345 | 368 | # (non-closed category definitions) |
346 | 369 | if ( array_key_exists( 'opened_at', $brace_match ) && |
347 | 370 | $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 ) ) { |
349 | 372 | # try to find matching opening brace for current non-closed closing brace |
350 | 373 | for ( $j = $i - 1; $j >= 0; $j-- ) { |
351 | 374 | $checked_brace = &$this->brace_matches[$brace_keys[$j]]; |
— | — | @@ -398,7 +421,7 @@ |
399 | 422 | $this->dbtokens = $brace_stack = array(); |
400 | 423 | $catId = 0; |
401 | 424 | $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 ); |
403 | 426 | $matching_closed_brace = ''; |
404 | 427 | $this->findMatchingBraces(); |
405 | 428 | foreach ( $this->rawtokens as $tkey => $token ) { |
— | — | @@ -421,11 +444,11 @@ |
422 | 445 | if ( array_key_exists( 'closed_at', $brace_match ) && |
423 | 446 | $brace_match['closed_at'] !== false ) { |
424 | 447 | # valid opening brace |
425 | | - array_push( $brace_stack, self::$matching_braces[$token] ); |
| 448 | + array_push( $brace_stack, $this->matching_braces[$token] ); |
426 | 449 | if ( array_key_exists( 'iscat', $brace_match ) ) { |
427 | 450 | # 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] ); |
430 | 453 | $toBeStored = false; |
431 | 454 | } |
432 | 455 | } elseif ( array_key_exists( 'opened_at', $brace_match ) && |
Index: trunk/extensions/QPoll/ctrl/question/qp_mixedquestion.php |
— | — | @@ -102,8 +102,8 @@ |
103 | 103 | } |
104 | 104 | # always borderless (mixed questions do not have spans) |
105 | 105 | $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}"; |
108 | 108 | $inp['class'] = 'check'; |
109 | 109 | $inp['type'] = $inputType; |
110 | 110 | $inp['name'] = $name; |
Index: trunk/extensions/QPoll/ctrl/question/qp_tabularquestion.php |
— | — | @@ -329,8 +329,8 @@ |
330 | 330 | } |
331 | 331 | $pview->setCategorySpan(); |
332 | 332 | 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}"; |
335 | 335 | # If type='unique()' question has more proposals than categories, such question is impossible to complete |
336 | 336 | if ( count( $this->mProposalText ) > count( $this->mCategories ) ) { |
337 | 337 | # if there was no previous errors, hightlight the whole row |
Index: trunk/extensions/QPoll/includes/qp_renderer.php |
— | — | @@ -132,12 +132,19 @@ |
133 | 133 | $tag['class'] = $className; |
134 | 134 | return; |
135 | 135 | } |
136 | | - if ( array_search( $className, explode( ' ', $tag['class'] ) ) === false ) { |
| 136 | + if ( !self::hasClassName( $tag['class'], $className ) ) { |
137 | 137 | $tag['class'] .= " $className"; |
138 | 138 | } |
139 | 139 | } |
140 | 140 | |
141 | 141 | /** |
| 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 | + /** |
142 | 149 | * Creates one tagarray row of the table |
143 | 150 | * @param $row a string/number value of cell or |
144 | 151 | * an array( "count"=>colspannum, "attribute"=>value, 0=>html_inside_tag ) |
Index: trunk/extensions/QPoll/view/results/qp_textquestiondataresults.php |
— | — | @@ -30,13 +30,25 @@ |
31 | 31 | $row[] = array( '__tag' => 'span', 'class' => 'prop_part', qp_Setup::entities( $token ) ); |
32 | 32 | } elseif ( is_array( $token ) ) { |
33 | 33 | # add a category definition with selected text answer (if any) |
| 34 | + $className = 'cat_part'; |
34 | 35 | if ( array_key_exists( $propkey, $ctrl->ProposalCategoryId ) && |
35 | 36 | ( $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 | + } |
37 | 42 | } else { |
38 | 43 | $text_answer = ''; |
| 44 | + $className .= ' cat_unanswered'; |
39 | 45 | } |
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 | + } |
41 | 53 | $titleAttr = ''; |
42 | 54 | foreach ( $token as &$option ) { |
43 | 55 | if ( $option !== $text_answer ) { |
— | — | @@ -46,7 +58,14 @@ |
47 | 59 | $titleAttr .= qp_Setup::entities( $option ); |
48 | 60 | } |
49 | 61 | } |
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 = ' '; |
| 66 | + } else { |
| 67 | + $text_answer = qp_Setup::entities( $text_answer ); |
| 68 | + } |
| 69 | + $row[] = array( '__tag' => 'span', 'class' => $className, 'title'=>$titleAttr, $text_answer ); |
51 | 70 | # move to the next category (if any) |
52 | 71 | $catId++; |
53 | 72 | } else { |
Index: trunk/extensions/QPoll/view/question/qp_textquestionview.php |
— | — | @@ -177,23 +177,32 @@ |
178 | 178 | } |
179 | 179 | |
180 | 180 | /** |
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. |
182 | 185 | * @param $viewtokens array of viewtokens |
183 | 186 | * @return tagarray |
184 | 187 | */ |
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}"; |
186 | 191 | $vr = $this->vr; |
187 | 192 | $vr->reset(); |
| 193 | + # category index, starting from 0 |
| 194 | + $ckey = 0; |
188 | 195 | foreach ( $viewtokens as $elem ) { |
189 | 196 | $vr->cell = array(); |
190 | 197 | if ( is_object( $elem ) ) { |
191 | 198 | if ( isset( $elem->options ) ) { |
192 | 199 | $className = 'cat_part'; |
193 | 200 | if ( $this->ctrl->mSubType === 'requireAllCategories' && $elem->unanswered ) { |
194 | | - $className = 'cat_noanswer'; |
| 201 | + $className .= ' cat_noanswer'; |
195 | 202 | } |
196 | 203 | if ( isset( $elem->interpError ) ) { |
197 | | - $className = 'cat_noanswer'; |
| 204 | + if ( !qp_Renderer::hasClassName( $className, 'cat_noanswer' ) ) { |
| 205 | + $className .= ' cat_noanswer'; |
| 206 | + } |
198 | 207 | # create view for proposal/category error message |
199 | 208 | $vr->cell[] = array( |
200 | 209 | '__tag' => 'span', |
— | — | @@ -210,16 +219,19 @@ |
211 | 220 | if ( !$elem->unanswered && $elem->value === '' && $elem->options[0] !== '' ) { |
212 | 221 | # input text pre-fill |
213 | 222 | $value = $elem->options[0]; |
214 | | - $className = 'cat_prefilled'; |
| 223 | + $className .= ' cat_prefilled'; |
215 | 224 | } |
216 | 225 | $input = array( |
217 | 226 | '__tag' => 'input', |
| 227 | + # unique (orderid,question,proposal,category) "coordinate" for javascript |
| 228 | + 'id' => "{$id_prefix}c{$ckey}", |
218 | 229 | 'class' => $className, |
219 | 230 | 'type' => $elem->type, |
220 | 231 | 'name' => $elem->name, |
221 | 232 | 'value' => qp_Setup::specialchars( $value ) |
222 | 233 | ); |
223 | | - if ( $elem->attributes['checked'] !== null ) { |
| 234 | + $ckey++; |
| 235 | + if ( $elem->type !== 'text' && $elem->attributes['checked'] === true ) { |
224 | 236 | $input['checked'] = 'checked'; |
225 | 237 | } |
226 | 238 | if ( $this->textInputStyle != '' ) { |
— | — | @@ -253,10 +265,13 @@ |
254 | 266 | } |
255 | 267 | $vr->cell[] = array( |
256 | 268 | '__tag' => 'select', |
| 269 | + # unique (poll,question,proposal,category) "coordinate" for javascript |
| 270 | + 'id' => "{$id_prefix}c{$ckey}", |
257 | 271 | 'class' => $className, |
258 | 272 | 'name' => $elem->name, |
259 | 273 | $html_options |
260 | 274 | ); |
| 275 | + $ckey++; |
261 | 276 | $vr->addCell(); |
262 | 277 | } elseif ( isset( $elem->error ) ) { |
263 | 278 | # create view for proposal/category error message |
— | — | @@ -305,8 +320,8 @@ |
306 | 321 | } |
307 | 322 | qp_Renderer::addRow( $questionTable, $row, $rowattrs, 'th', $attribute_maps ); |
308 | 323 | } |
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 ); |
311 | 326 | $rowattrs = array( 'class' => $propview->rowClass ); |
312 | 327 | if ( $this->transposed ) { |
313 | 328 | qp_Renderer::addColumn( $questionTable, $prop, $rowattrs ); |