View Issue Details

This bug affects 1 person(s).
 6
IDProjectCategoryView StatusLast Update
20283Bug reportsAccessibilitypublic2025-10-03 16:04
Reporterdan24 Assigned To 
PrioritynoneSeverityminor 
Status newResolutionopen 
Summary20283: No focus on ranking question - no possibility to navigate with keyboard
Description

Hello,

With Limesurvey V3.28.77 and it's the same with V6.14, the ranking question didn't tke the focus (vanilla theme for exemple).
So it's not possible to make an answer on this question type with the keyboard?

Steps To Reproduce

Steps to reproduce

Make a survey , with Vanilla thème, where there is question type "ranking" and try to answer it like a visually impaired person

Expected result

with the tab key il should be able to select and validate my choice

Actual result

No focus, no keyboard navigation

TagsNo tags attached.
Bug heat6
Complete LimeSurvey version number (& build)V6.14
I will donate to the project if issue is resolvedNo
Browserfirefox
Database type & versionMariadb
Server OS (if known)
Webserver software & version (if known)nginx
PHP Version7.4

Users monitoring this issue

DenisChenu

Activities

DenisChenu

DenisChenu

2025-10-03 15:40

developer   ~83528

Not only focus (adding tabindex didn't fix the issue)

DenisChenu

DenisChenu

2025-10-03 15:44

developer   ~83529

visually impaired person

Not really true : blind people have the dropdown.
It can be tested using a screen reader

But yes, inaccessible for a lot of user.

See https://gitlab.com/SondagesPro/QuestionTheme/rankingDropDown for an alternative (dropdown for all)

dan24

dan24

2025-10-03 15:51

reporter   ~83530

In V3.28.77 we are doing some modifications for a keyboard navigation, and we report it in the V6.14
I am sending them to you for information or advice.
In the V3 the 3 following files have a modification :

  • application/helpers/qanda_helper.php
  • application/views/survey/questions/answer/ranking/answer.twig
  • application/views/survey/questions/answer/ranking/rows/answer_row.twig
    In V6 the modification in - application/helpers/qanda_helper.php is now in application/core/QuestionTypes/RankingStyle/RenderRanking.phpvi
RenderRanking.php (7,152 bytes)   
<?php

/**
 * RenderClass for Boilerplate Question
 *  * The ia Array contains the following
 *  0 => string qid
 *  1 => string sgqa
 *  2 => string questioncode
 *  3 => string question
 *  4 => string type
 *  5 => string gid
 *  6 => string mandatory,
 *  7 => string conditionsexist,
 *  8 => string usedinconditions
 *  0 => string used in group.php for question count
 * 10 => string new group id for question in randomization group (GroupbyGroup Mode)
 *
 */
class RenderRanking extends QuestionBaseRenderer
{
    private $inputnames = [];
    private $aDisplayAnswers = [];

    private $iMaxSubquestions;
    private $mMaxAnswers;
    private $mMinAnswers;
    private $sLabeltext;

    public function __construct($aFieldArray, $bRenderDirect = false)
    {
        parent::__construct($aFieldArray, $bRenderDirect);
        $this->setAnsweroptions();
        $this->iMaxSubquestions = ((int) $this->getQuestionAttribute('max_subquestions')) > 0
            ? ((int) $this->getQuestionAttribute('max_subquestions'))
            : $this->getAnswerCount();

        $this->mMaxAnswers = trim((string) $this->getQuestionAttribute('max_answers')) != ''
            ? (
                ($this->iMaxSubquestions < $this->getAnswerCount())
                ? "min(" . trim((string) $this->getQuestionAttribute('max_answers')) . "," . $this->iMaxSubquestions . ")"
                : trim((string) $this->getQuestionAttribute('max_answers'))
              )
            : $this->iMaxSubquestions;

        $this->mMinAnswers = $this->setDefaultIfEmpty($this->getQuestionAttribute('min_answers'), 0);
    }

    public function getMainView()
    {
        return '/survey/questions/answer/ranking';
    }

    public function getRows()
    {
        // Get the max number of line needed
        $iMaxLine = (
            (ctype_digit((string) $this->mMaxAnswers) && intval($this->mMaxAnswers) < $this->iMaxSubquestions)
                ? $this->mMaxAnswers
                : $this->iMaxSubquestions
        );

        $sSelects = '';
        $curValue = '';

        for ($i = 1; $i <= $iMaxLine; $i++) {
            $myfname = $this->sSGQA . $i;
            $this->sLabeltext = ($i == 1) ? gT('First choice') : sprintf(gT('Choice of rank %s'), $i);
			aItemData = [];

            if (!$_SESSION['survey_' . Yii::app()->getConfig('surveyID')][$myfname]) {
                $aItemData[] = array(
                    'value'      => '',
                    'selected'   => 'SELECTED',
                    'classes'    => '',
                    'id'         => '',
                    'optiontext' => gT('Please choose...'),
                );
            }

            foreach ($this->aAnswerOptions[0] as $oAnswer) {
                $this->aDisplayAnswers[$oAnswer->aid] = array_merge($oAnswer->attributes, $oAnswer->answerl10ns[$this->sLanguage]->attributes);
                $mSessionValue = $this->setDefaultIfEmpty($_SESSION['survey_' . Yii::app()->getConfig('surveyID')][$myfname], false);

                if ($mSessionValue == $oAnswer->code) {
                    $selected = SELECTED;
                    $curValue = $mSessionValue;
                } else {
                    $selected = '';
                }

                $aItemData[] = array(
                    'value' => $oAnswer->code,
                    'selected' => $selected,
                    'classes' => '',
                    'optiontext' => $oAnswer->answerl10ns[$this->sLanguage]->answer
                );
            }
            //Redbug PNEWEB 9627 ajout code en cours et passage de variable dans doRender qui suit
            $j = $i-1;
            $codeEnCours = $itemDatas[$i]['value'];

            $sSelects .= Yii::app()->twigRenderer->renderQuestion(
                $this->getMainView() . '/rows/answer_row',
                array(
                    'myfname' => $myfname,
                    'labeltext' => $this->sLabeltext,
                    'options' => $aItemData,
                    'thisvalue' => $curValue,
                    'answers' => $answers,
                    'rankingName' => $ia[1],
                    'codeEnCours' => $codeEnCours
                ),
                true
            );

            $inputnames[] = $myfname
			}

        return $sSelects;
    }

    public function render($sCoreClasses = '')
    {
        $answer = '';

        $sCoreClasses = "ls-answers answers-lists select-sortable-lists " . $sCoreClasses;
        if (!empty($this->getQuestionAttribute('time_limit'))) {
            $answer .= $this->getTimeSettingRender();
        }

        $rankingTranslation = 'LSvar.lang.rankhelp="' . gT("Double-click or drag-and-drop items in the left list to move them to the right - your highest ranking item should be on the top right, moving through to your lowest ranking item.", 'js') . ' Sans souris, Shift + r remplace le double-click sur l\'élément ayant le focus.'. '";';
        $rankingTranslation .= 'LSvar.lang.rankadvancedhelp="' . gT("Drag or double-click images into order.", 'js') . '";';
        $this->addScript("rankingTranslation", $rankingTranslation, CClientScript::POS_BEGIN);
        //$this->applyScripts();

        if (trim((string) $this->getQuestionAttribute('choice_title', App()->language)) != '') {
            $choice_title = htmlspecialchars(trim((string) $this->getQuestionAttribute('choice_title', App()->language)), ENT_QUOTES);
        } else {
            $choice_title = gT("Available items", 'html');
        }

        if (trim((string) $this->getQuestionAttribute('rank_title', App()->language)) != '') {
            $rank_title = htmlspecialchars(trim((string) $this->getQuestionAttribute('rank_title', App()->language)), ENT_QUOTES);
        } else {
            $rank_title = gT("Your ranking", 'html');
        }

        $answer .=  Yii::app()->twigRenderer->renderQuestion($this->getMainView() . '/answer', array(
            'coreClass'         => $sCoreClasses,
            'sSelects'          => $this->getRows(),
            'thisvalue'         => $this->mSessionValue,
            'answers'           => $this->aDisplayAnswers,
            'myfname'           => $this->sSGQA,
            'labeltext'         => $this->sLabeltext,
            'qId'               => $this->oQuestion->qid,
            'rankingName'       => $this->sSGQA,
            'basename'          => $this->sSGQA,
            'max_answers'       => $this->mMaxAnswers,
            'min_answers'       => $this->mMinAnswers,
            'choice_title'      => $choice_title,
            'rank_title'        => $rank_title,
            'showpopups'        => $this->getQuestionAttribute("showpopups"),
            'samechoiceheight'  => $this->getQuestionAttribute("samechoiceheight"),
			), true);

        $this->registerAssets();
        $inputnames[] = $this->sSGQA;
        return array($answer, $this->inputnames);
    }
}
            'samelistheight'    => $this->getQuestionAttribute("samelistheight"),
RenderRanking.php (7,152 bytes)   
answer_row.twig (1,117 bytes)   
{# <?php
/**
 * Ranking question, item Html
 * @var $value
 * @var $selected
 * @var $classes
 * @var $id
 * @var $optiontext
 */
?> #}
<!-- Redbug 9627 ajout de javascript pour activer le focus -->
<li class="select-item mb-3">
    <label for="answer{{myfname}}" class="control-label col-md-4">
        {{ processString(labeltext) }}
    </label>
    <div class="col-md-8">
        <select  tabindex="0" class='form-select' name="{{myfname}}" id="answer{{myfname}}">
            {% for option in options %}
                <option value="{{ option.value }}" {{ option.selected }} class='{{option.classes}}'>
                    {{ flatString(processString(option.optiontext,1)) }}
                </option>
            {% endfor %}
        </select>
        <!-- Hidden form: maybe can be replaced with ranking.js -->
        <input type="hidden" id="java{{myfname}}" disabled="disabled" value="{{thisvalue}}"/>
    </div>
</li>
<script>
document.addEventListener("DOMContentLoaded", function() {
    setFocus("answer{{myfname}}","javatbd{{rankingName}}{{codeEnCours}}");
});
</script>
answer_row.twig (1,117 bytes)   
answer.twig (5,515 bytes)   
{#
!!!! BECAREFUL: ONLY FOR TESTING !!!!!
!!!! DON'T START TO TRANSLATE ALL VIEWS BASED ON THIS MODEL !!!!!

!!!! IT WILL PROBABLY FIRST NEED TO CHANGE  THE TWIG TEMPLATE SYNTAX TO AVOID CONFLICT WITH EXPRESSION MANAGER !!!!

/**
 * Ranking question, item list header Html
 *
 * @var $sOptions         : the select options, generated with the view answer_row.php
 *
 * @var $name
 * @var $myfname
 * @var $labeltext
 * @var $rankId
 * @var $rankingName
 * @var $max_answers
 * @var $min_answers
 * @var $qid
 * @var $choice_title
 * @var $rank_title
 * @var $rank_help
 * @var $showpopups
 * @var $samechoiceheight
 * @var $samelistheight
 **** Additional attributes:
 * @var question_template_attribute.show_handle
 * @var question_template_attribute.only_pull
 * @var question_template_attribute.visualize
 */
#}

<!-- Redbug 9627 ajout de javascript pour simuler le double click via les touches shift + r et la prise de focus tabindex -->
<script>
function setFocus(inputId, targetId) {
        const inputElement = document.getElementById(inputId);
        const targetElement = document.getElementById(targetId);

        inputElement.addEventListener('focus', () => {
            targetElement.focus();
        });
}
</script>

<!-- answer -->
<div class="{{coreClass}}">
    <ul class="list-unstyled ls-js-hidden-sr answers-list select-list " role="group" aria-labelledby="ls-question-text-{{basename}}">
            {# rows/answer_row.twig #}
            {{sSelects}}
    </ul>
    <div class="ls-no-js-hidden answers-list{{ samechoiceheight ? " list-samechoiceheight": "" }} {{ samelistheight ? " list-samelistheight": "" }} row" aria-hidden="true">
        <div class="col-md-6 col-6 ranking-available-items">
            <strong class="sortable-subtitle sortable-rank-subtitle">{{choice_title}}</strong>
			<ul id="sortable-choice-{{qId}}" class="sortable-choice sortable-list list-group">
                {% for ansrow in  answers %}
                    <li id="javatbd{{rankingName}}{{ansrow.code}}" tabindex="0" class="ls-choice list-group-item answer-item sortable-item grabable sortable-enable" data-value="{{ansrow.code}}">
                        {{ processString(ansrow.answer) }}
                        <span class="grabable selector__dragHandle d-none float-end">
                            <svg class="" width="9" height="14" viewBox="0 0 9 14" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path fill-rule="evenodd" clip-rule="evenodd" d="M0.4646 0.125H3.24762V2.625H0.4646V0.125ZM6.03064 0.125H8.81366V2.625H6.03064V0.125ZM0.4646 5.75H3.24762V8.25H0.4646V5.75ZM6.03064 5.75H8.81366V8.25H6.03064V5.75ZM0.4646 11.375H3.24762V13.875H0.4646V11.375ZM6.03064 11.375H8.81366V13.875H6.03064V11.375Z" fill="currentColor"/>
                            </svg>
                        </span>
                    </li>
                    <script>
                        document.getElementById("javatbd{{rankingName}}{{ansrow.code}}").addEventListener('keydown', function(event) {
                                // Vérifier si les touches shift et r sont pressées
                                if (event.shiftKey && event.key === "R") {
                                        let focusedElement = document.activeElement;

                                        if (focusedElement) {
                                                let dblclickEvent = new MouseEvent("dblclick", {
                                                bubbles: true,
                                                cancelable: true,
                                                view: window
                                                });
                                                focusedElement.dispatchEvent(dblclickEvent);
                                        }
                                }
                        });
                    </script>
                {% endfor %}
                <li class="d-none ls-remove"></li>
            </ul>
        </div>
        <div class="col-md-6 col-6 ranking-sorted-items">
            <strong class="sortable-subtitle sortable-rank-subtitle">{{rank_title}}</strong>
            <ul id="sortable-rank-{{qId}}" class="sortable-rank sortable-list list-group">
                <li class="d-none ls-remove"></li>
            </ul>
        </div>
    </div>
</div>

{% set script %}
try{
 var ranking{{qId}} = new RankingQuestion({
        max_answers      : "{{ processString("{" ~ max_answers ~ "}", 1) }}",
        min_answers      : "{{ processString("{" ~ min_answers ~ "}", 1) }}",
        showpopups       : "{{showpopups}}",
        samechoiceheight : "{{samechoiceheight}}",
		samelistheight   : "{{samelistheight}}",
        rankingName      : "{{rankingName}}",
        questionId       : "{{qId}}"
    });
 ranking{{qId}}.init()
} catch(e){}
{% endset %}
{{ registerPackage('sortablejs') }}
{{ registerPackage('question-ranking') }}
{{ registerScript(
    'RankingQuestionTranslate'~qId,
    'LSvar.lang.rankhelp="' ~ gT("Double-click or drag-and-drop items in the left list to move them to the right - your highest ranking item should be on the top right, moving through to your lowest ranking item.") ~ '";', 'POS_BEGIN')
}}
{{ registerScript( 'RankingQuestion'~qId, script, 'POS_POSTSCRIPT') }}


<!-- end of answer -->


            <!-- @todo : move htmlblock at the good place -->
answer.twig (5,515 bytes)   
dan24

dan24

2025-10-03 16:04

reporter   ~83531

Ok thank you for :
See https://gitlab.com/SondagesPro/QuestionTheme/rankingDropDown for an alternative (dropdown for all)

I replace Ranking question by it for accessibility audit

thanks
Dan

Issue History

Date Modified Username Field Change
2025-10-03 15:30 dan24 New Issue
2025-10-03 15:40 DenisChenu Note Added: 83528
2025-10-03 15:40 DenisChenu Bug heat 0 => 2
2025-10-03 15:40 DenisChenu Issue Monitored: DenisChenu
2025-10-03 15:40 DenisChenu Bug heat 2 => 4
2025-10-03 15:44 DenisChenu Note Added: 83529
2025-10-03 15:51 dan24 Note Added: 83530
2025-10-03 15:51 dan24 File Added: RenderRanking.php
2025-10-03 15:51 dan24 File Added: answer_row.twig
2025-10-03 15:51 dan24 File Added: answer.twig
2025-10-03 15:51 dan24 Bug heat 4 => 6
2025-10-03 16:04 dan24 Note Added: 83531