http://www.mediawiki.org/wiki/Special:Code/MediaWiki/100690
Revision: 100690
Author: questpc
Date: 2011-10-25 11:52:58 +0000 (Tue, 25 Oct 2011)
Log Message:
-----------
Fixed regression in question type text when prefilled text inputs were not
displayed. Added textarea and select multiple view modes for question type text
categories via introduced height and multiple category attributes. Allow
unquoted integer value for xml-like attributes.
Modified Paths:
--------------
trunk/extensions/QPoll/clientside/qp_user.js
trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
trunk/extensions/QPoll/qp_user.php
trunk/extensions/QPoll/specials/qp_results.php
trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
trunk/extensions/QPoll/view/question/qp_textquestionview.php
Modified: trunk/extensions/QPoll/clientside/qp_user.js
===================================================================
--- trunk/extensions/QPoll/clientside/qp_user.js 2011-10-25 11:52:39 UTC
(rev 100689)
+++ trunk/extensions/QPoll/clientside/qp_user.js 2011-10-25 11:52:58 UTC
(rev 100690)
@@ -48,7 +48,7 @@
/**
* Parses coordinate of poll's input stored in id and
* stores it into self.catCoord;
- * @param id id attribute of input / select element
+ * @param id id attribute of input / textarea / select element
* @return true, when value of id has valid coordinate, false
otherwise.
*/
setCatCoord : function( id ) {
@@ -87,9 +87,14 @@
applyRadio : function( catElem ) {
if ( self.radioIsClicked ) {
// deselect all inputs
- if ( catElem.nodeName == 'SELECT' ||
catElem.type == 'text' ) {
+ if ( catElem.nodeName != 'INPUT' ||
catElem.type == 'text' ) {
+ // text controls
catElem.value = '';
+ if ( catElem.nodeName == 'TEXTAREA' ) {
+ catElem.innerHTML = '';
+ }
} else {
+ // switching controls
catElem.checked = false;
}
} else {
@@ -160,6 +165,15 @@
}
},
+ setTextRowHandler : function( parent, tagName ) {
+ var tags = parent.getElementsByTagName( tagName );
+ for ( j = 0; j < tags.length; j++ ) {
+ if ( tags[j].id && tags[j].id.slice( 0, 2 ) ==
'tx' ) {
+ addEvent( tags[j], "click",
self.clickTextRow );
+ }
+ }
+ },
+
/**
* Prepare the Poll for "javascriptable" browsers
*/
@@ -198,13 +212,8 @@
}
}
}
- 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 );
- }
- }
+ self.setTextRowHandler( bodyContentDiv[i],
'select' );
+ self.setTextRowHandler( bodyContentDiv[i],
'textarea' );
}
}
};
Modified: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_textquestion.php 2011-10-25
11:52:39 UTC (rev 100689)
+++ trunk/extensions/QPoll/ctrl/question/qp_textquestion.php 2011-10-25
11:52:58 UTC (rev 100690)
@@ -23,23 +23,34 @@
# whether the current option has xml-like attributes specified
var $hasAttributes = false;
+ ## Category attributes;
+ # Defined as xml-like attribute in the first element of options list.
var $attributes = array(
- ## a value of input text field width in 'em'
- # possible values: null, positive int
- # defined as first element xml-like attribute of options list,
for example:
- # <<:: width="12">> or <<:: width="15"|test>>
- # currently, it is used only for text inputs (not for
select/option list)
+ ## width of input text field
+ # possible values: null, positive int, 'auto'
+ # <<:: width="12">> or <<:: width="15"|test>> or <<::
width="auto"|asd|fgh>>
+ # currently, it is used only for text input / textarea (not for
select/option list)
'width' => null,
+ ## number of text lines in an select / textarea
+ # possible values: null, positive int, 'auto'
+ # when there is no options or only one option, it produces
an textarea
+ # when there is more than one option, it produces a
scrollable select / options list
+ # value 'auto' is meaningful only when there are more than
one option given;
+ # <<:: height="4">> or <<:: height="10"|prefilled text>>
+ 'height' => null,
## whether the text options of current category has to be
sorted;
- # possible values: null (do not sort), 'asc', 'desc'
- # defined as first element xml-like attribute of options list,
for example:
+ # possible values: null (do not sort), 'asc', 'desc'
# <<:: sorting="desc"|a|b|c>>
'sorting' => null,
## whether the checkbox type option of current category has to
be checked by default;
- # possible value: null (not checked), not null (checked)
- # defined as first element xml-like attribute of options list,
for example:
+ # possible values: null (not checked), not null (checked)
# <[checked=""]>
- 'checked' => null
+ 'checked' => null,
+ ## whether the select for current category can have multiple
options selected
+ # possible values: null (no multiple selection), not null
(multiple selection)
+ # it is meaningful only for text categories with multiple
options defined:
+ # <<:: multiple=""|1|2|3>>
+ 'multiple' => null
);
# a pointer to last element in $this->input_options array
var $iopt_last;
@@ -103,7 +114,7 @@
# because it is checked in
$this->addEmptyOption()
$this->hasAttributes = true;
# parse attributes string
- $option_attributes =
qp_Setup::getXmlLikeAttributes( $matches[1], array( 'width', 'sorting' ) );
+ $option_attributes =
qp_Setup::getXmlLikeAttributes( $matches[1], array( 'width', 'height',
'sorting', 'multiple' ) );
# apply attributes to current option
foreach ( $option_attributes as $attr_name =>
$attr_val ) {
$this->attributes[$attr_name] =
$attr_val;
@@ -256,14 +267,21 @@
function loadProposalCategory( qp_TextQuestionOptions $opt,
$proposalId, $catId ) {
global $wgContLang;
$name = "q{$this->mQuestionId}p{$proposalId}s{$catId}";
- # default value for unanswered category
- # boolean true "checked" checkbox / radiobutton
- # string - text input / select option value
- $text_answer = false;
+ $answered = false;
+ $text_answer = '';
# try to load from POST data
- if ( $this->poll->mBeingCorrected && $this->mRequest->getVal(
$name ) !== null ) {
+ if ( $this->poll->mBeingCorrected &&
+ ( $ta = $this->mRequest->getArray( $name ) )
!== null ) {
if ( $opt->type === 'text' ) {
- if ( ( $ta = trim( $this->mRequest->getText(
$name ) ) ) != '' ) {
+ if ( count( $ta ) === 1 ) {
+ # fallback to WebRequest::getText(),
because it offers useful preprocessing
+ $ta = trim( $this->mRequest->getText(
$name ) );
+ } else {
+ # select multiple values are separated
with new lines
+ $ta = implode( "\n", array_map( 'trim',
$ta ) );
+ }
+ if ( $ta != '' ) {
+ $answered = true;
if ( strlen( $ta ) >
qp_Setup::$field_max_len['text_answer'] ) {
$text_answer =
$wgContLang->truncate( $ta, qp_Setup::$field_max_len['text_answer'] , '' );
} else {
@@ -271,34 +289,28 @@
}
}
} else {
- $text_answer = true;
+ $answered = true;
}
}
# try to load from pollStore
# pollStore optionally overrides POST data
- $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 ( ( $prev_text_answer = $this->answerExists( $opt->type,
$proposalId, $catId ) ) !== false ) {
+ $answered = true;
+ if ( is_string( $prev_text_answer ) ) {
+ $text_answer = $prev_text_answer;
}
}
- if ( $text_answer !== false ) {
+ if ( $answered !== false ) {
# add category to the list of user answers for current
proposal (row)
$this->mProposalCategoryId[ $proposalId ][] = $catId;
- if ( is_string( $text_answer ) ) {
- $this->mProposalCategoryText[ $proposalId ][] =
$text_answer;
- } else {
- $this->mProposalCategoryText[ $proposalId ][] =
'';
- if ( $opt->type !== 'text' ) {
- $opt->attributes['checked'] = true;
- }
+ $this->mProposalCategoryText[ $proposalId ][] =
$text_answer;
+ 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 && $text_answer === false );
+ $this->propview->addCatDef( $opt, $name, $text_answer,
$this->poll->mBeingCorrected && !$answered );
}
/**
Modified: trunk/extensions/QPoll/qp_user.php
===================================================================
--- trunk/extensions/QPoll/qp_user.php 2011-10-25 11:52:39 UTC (rev 100689)
+++ trunk/extensions/QPoll/qp_user.php 2011-10-25 11:52:58 UTC (rev 100690)
@@ -469,8 +469,9 @@
$attr_vals = array();
$match = array();
foreach ( $attr_list as $attr_name ) {
- preg_match( '/' . $attr_name . '\s?=\s?"(.*?)"/u',
$attr_str, $match );
- $attr_vals[$attr_name] = ( count( $match ) > 1 ) ?
$match[1] : null;
+ preg_match( '/' . $attr_name .
'\s?=\s?(?:"(.*?)"|(\d+))/u', $attr_str, $match );
+ # array_pop() "prefers" to match (\d+), when available
+ $attr_vals[$attr_name] = ( count( $match ) > 1 ) ?
array_pop( $match ) : null;
}
return $attr_vals;
}
Modified: trunk/extensions/QPoll/specials/qp_results.php
===================================================================
--- trunk/extensions/QPoll/specials/qp_results.php 2011-10-25 11:52:39 UTC
(rev 100689)
+++ trunk/extensions/QPoll/specials/qp_results.php 2011-10-25 11:52:58 UTC
(rev 100690)
@@ -313,7 +313,12 @@
@unlink( $xls_fname );
exit();
} catch ( Exception $e ) {
- die( "Error while exporting poll statistics to Excel
table\n" );
+ if ( $e instanceof MWException ) {
+ $e->reportHTML();
+ exit();
+ } else {
+ die( "Error while exporting poll statistics to
Excel table\n" );
+ }
}
}
Modified: trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
===================================================================
--- trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
2011-10-25 11:52:39 UTC (rev 100689)
+++ trunk/extensions/QPoll/view/proposal/qp_textquestionproposalview.php
2011-10-25 11:52:58 UTC (rev 100690)
@@ -45,6 +45,8 @@
* @param $name string name of input/select element (used in the
view)
* @param $text_answer string user's POSTed category answer
* (empty string '' means no answer)
+ * @param $unanswered boolean indicates whether the category of
submitted poll
+ * was non-blank (true) or not (false)
* @return stdClass object with viewtokens entry
*/
function addCatDef( qp_TextQuestionOptions $opt, $name, $text_answer,
$unanswered ) {
@@ -57,10 +59,7 @@
# property 'value' contains value previousely chosen
# by user (if any)
# property 'attributes' contain extra atttibutes of current
category definition
- # property 'unanswered' boolean
- # true - the question was POSTed but category is
unanswered
- # false - the question was not POSTed or category is
answered
- $this->viewtokens[] = (object) array(
+ $viewtoken = (object) array(
'type' => $opt->type,
'options' => $opt->input_options,
'name' => $name,
@@ -68,6 +67,21 @@
'unanswered' => $unanswered,
'attributes' => $opt->attributes
);
+ # fix values of measurable attributes (allow only non-negative
integer values)
+ # zero value means attribute is unused
+ foreach ( array( 'width', 'height' ) as $measurable ) {
+ $val = &$viewtoken->attributes[$measurable];
+ if ( $val === null ) {
+ $val = 0;
+ } elseif ( is_numeric( $val ) ) {
+ if ( ( $val = intval( $val ) ) < 1 ) {
+ $val = 0;
+ }
+ } else {
+ $val = 'auto';
+ }
+ }
+ $this->viewtokens[] = $viewtoken;
$this->lastTokenType = 'category';
}
Modified: trunk/extensions/QPoll/view/question/qp_textquestionview.php
===================================================================
--- trunk/extensions/QPoll/view/question/qp_textquestionview.php
2011-10-25 11:52:39 UTC (rev 100689)
+++ trunk/extensions/QPoll/view/question/qp_textquestionview.php
2011-10-25 11:52:58 UTC (rev 100690)
@@ -51,6 +51,7 @@
# depending on $this->tabularDisplay value
var $row;
# tagarray with error elements will be merged into adjascent cells
+ # (otherwise tabular layout will be broken by proposal errors)
var $error;
# tagarray with current cell builded for row
# cell contains one or multiple tags, describing proposal part or
category
@@ -63,6 +64,9 @@
$this->reset( '' );
}
+ /**
+ * Prepare current instance for new proposal row
+ */
function reset( $id_prefix ) {
$this->id_prefix = $id_prefix;
$this->row = array();
@@ -72,6 +76,9 @@
$this->ckey = 0;
}
+ /**
+ * Add proposal error tagarray
+ */
function addError( $elem ) {
$this->cell[] = array(
'__tag' => 'span',
@@ -80,57 +87,98 @@
);
}
+ /**
+ * Add category as input type text / checkbox / radio / textarea
tagarray
+ */
function addInput( $elem, $className ) {
+ $tagName = ( $elem->type === 'text' &&
$elem->attributes['height'] !== 0 ) ? 'textarea' : 'input';
+ $lines_count = 1;
+ # get category value
$value = $elem->value;
# check, whether the definition of category has "pre-filled"
value
# single, non-unanswered, non-empty option is a pre-filled value
if ( !$elem->unanswered && $elem->value === '' &&
$elem->options[0] !== '' ) {
# input text pre-fill
$value = $elem->options[0];
+ if ( $tagName === 'textarea' ) {
+ # oversimplicated regexp, but it's enough for
our needs
+ $value = preg_replace(
'/<br[\sA-Z\d="]*\/{0,1}>/i', "\n", $value, -1, $lines_count );
+ $lines_count++;
+ }
$className .= ' cat_prefilled';
}
- $input = array(
- '__tag' => 'input',
+ $tag = array(
+ '__tag' => $tagName,
# unique
(poll_type,order_id,question,proposal,category) "coordinate" for javascript
'id' => "{$this->id_prefix}c{$this->ckey}",
'class' => $className,
- 'type' => $elem->type,
'name' => $elem->name,
- 'value' => qp_Setup::specialchars( $value )
);
+ if ( $tagName === 'input' ) {
+ $tag['type'] = $elem->type;
+ $tag['value'] = qp_Setup::specialchars( $value );
+ } else { /* 'textarea' */
+ $tag[] = qp_Setup::specialchars( $value );
+ if ( is_int( $elem->attributes['height'] ) ) {
+ $tag['rows'] = $elem->attributes['height'];
+ } else { /* 'auto' */
+ # todo: allow multiline prefilled text and
calculate number of new lines
+ $tag['rows'] = $lines_count;
+ }
+ }
$this->ckey++;
- if ( $elem->type !== 'text' && $elem->attributes['checked'] ===
true ) {
- $input['checked'] = 'checked';
+ if ( $elem->type === 'text' ) {
+ # input type text and textarea
+ if ( $this->owner->textInputStyle != '' ) {
+ # apply poll's textwidth attribute
+ $tag['style'] = $this->owner->textInputStyle;
+ }
+ if ( $elem->attributes['width'] !== 0 ) {
+ # apply current category width attribute
+ if ( is_int( $elem->attributes['width'] ) ) {
+ $tag['style'] = 'width:' .
$elem->attributes['width'] . 'em;';
+ } else { /* 'auto' */
+ $tag['style'] = 'width:99%;';
+ }
+ }
+ } else {
+ # checkbox or radiobutton
+ if ( $elem->attributes['checked'] === true ) {
+ $tag['checked'] = 'checked';
+ }
}
- if ( $elem->type === 'text' && $this->owner->textInputStyle !=
'' ) {
- # apply poll's textwidth attribute
- $input['style'] = $this->owner->textInputStyle;
- }
- if ( $elem->attributes['width'] !== null ) {
- # apply current category width attribute
- $input['style'] = 'width:' . intval(
$elem->attributes['width'] ) . 'em;';
- }
- $this->cell[] = $input;
+ $this->cell[] = $tag;
}
+ /**
+ * Add category as select / option list tagarray
+ */
function addSelect( $elem, $className ) {
if ( $elem->options[0] !== '' ) {
- # default element in select/option set always must be
empty option
+ # default element in select/option set always must be
an empty option
array_unshift( $elem->options, '' );
}
$html_options = array();
+ # prepare the list of selected values
+ if ( $elem->attributes['multiple'] !== null ) {
+ # new lines are separator for selected multiple options
+ $selected_values = explode( "\n", $elem->value );
+ } else {
+ $selected_values = array( $elem->value );
+ }
+ # generate options list
foreach ( $elem->options as $option ) {
$html_option = array(
'__tag' => 'option',
'value' => qp_Setup::entities( $option ),
qp_Setup::specialchars( $option )
);
- if ( $option === $elem->value ) {
+ if ( in_array( $option, $selected_values ) ) {
$html_option['selected'] = 'selected';
}
$html_options[] = $html_option;
}
- $this->cell[] = array(
+ $select = array(
'__tag' => 'select',
# unique
(poll_type,order_id,question,proposal,category) "coordinate" for javascript
'id' => "{$this->id_prefix}c{$this->ckey}",
@@ -138,9 +186,29 @@
'name' => $elem->name,
$html_options
);
+ # multiple options 'name' attribute should have array hint []
+ if ( $elem->attributes['multiple'] !== null ) {
+ $select['multiple'] = 'multiple';
+ $select['name'] .= '[]';
+ }
+ # determine visual height of select options list
+ if ( ( $size = $elem->attributes['height'] ) !== 0 ) {
+ if ( is_int( $size ) ) {
+ if ( count( $elem->options ) < $size ) {
+ $size = count( $elem->options );
+ }
+ } else { /* 'auto' */
+ $size = count( $elem->options );
+ }
+ $select['size'] = $size;
+ }
+ $this->cell[] = $select;
$this->ckey++;
}
+ /**
+ * Add tagarray representation of proposal part
+ */
function addProposalPart( $elem ) {
$this->cell[] = array(
'__tag' => 'span',
@@ -149,6 +217,11 @@
);
}
+ /**
+ * Build "final" cell which contain tagarray representation of
+ * proposal parts, proposal errors and one adjascent category
+ * and then add it to the row
+ */
function addCell() {
if ( count( $this->error ) > 0 ) {
# merge previous errors to current cell
@@ -179,6 +252,11 @@
# whether the resulting display table should be transposed
# meaningful only when $this->tabularDisplay is true
var $transposed = false;
+ # how many characters will hold horizontal line of textarea;
+ # currently is unused, because textarea 'cols' attribute renders
+ # poorly in table cells in modern versions of Firefox, so
+ # we are using CSS $this->textInputStyle instead
+ # var $textwidth = 0;
# default style of text input
var $textInputStyle = '';
@@ -206,9 +284,11 @@
$this->transposed = strpos( $layout, 'transpose' ) !==
false;
}
if ( $textwidth !== null ) {
- $textwidth = intval( $textwidth );
- if ( $textwidth > 0 ) {
+ if ( is_numeric( $textwidth ) &&
+ $textwidth = intval( $textwidth ) > 0 )
{
$this->textInputStyle = 'width:' . $textwidth .
'em;';
+ } elseif ( $textwidth === 'auto' ) {
+ $this->textInputStyle = 'width:auto;';
}
}
}
@@ -290,9 +370,9 @@
# create view for
proposal/category error message
$vr->addError( $elem );
}
- # create view for the input options part
+ # create view for the input / textarea
options part
if ( count( $elem->options ) === 1 ) {
- # one option produces html text
/ radio / checkbox input
+ # one option produces html text
/ radio / checkbox input or an textarea
$vr->addInput( $elem,
$className );
$vr->addCell();
continue;
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs