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 MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs