/**
     * Get the HTML fragment corresponding to the ext key editing widget
     * @param WebPage $oP The web page used for all the output
     * @param Hash $aArgs Extra context arguments
     * @return string The HTML fragment to be inserted into the page
     */
    public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
    {
        if (!is_null($bSearchMode)) {
            $this->bSearchMode = $bSearchMode;
        }
        $sTitle = addslashes($sTitle);
        $oPage->add_linked_script('../js/extkeywidget.js');
        $oPage->add_linked_script('../js/forms-json-utils.js');
        $bCreate = !$this->bSearchMode && !MetaModel::IsAbstract($this->sTargetClass) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
        $bExtensions = true;
        $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
        $sAttrFieldPrefix = $this->bSearchMode ? '' : 'attr_';
        $sHTMLValue = "<span style=\"white-space:nowrap\">";
        // no wrap
        $sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
        if ($this->bSearchMode) {
            $sWizHelper = 'null';
            $sWizHelperJSON = "''";
            $sJSSearchMode = 'true';
        } else {
            if (isset($aArgs['wizHelper'])) {
                $sWizHelper = $aArgs['wizHelper'];
            } else {
                $sWizHelper = 'oWizardHelper' . $sFormPrefix;
            }
            $sWizHelperJSON = $sWizHelper . '.UpdateWizardToJSON()';
            $sJSSearchMode = 'false';
        }
        if (is_null($oAllowedValues)) {
            throw new Exception('Implementation: null value for allowed values definition');
        } elseif ($oAllowedValues->Count() < $iMaxComboLength) {
            // Discrete list of values, use a SELECT or RADIO buttons depending on the config
            switch ($sDisplayStyle) {
                case 'radio':
                case 'radio_horizontal':
                case 'radio_vertical':
                    $sValidationField = "<span id=\"v_{$this->iId}\"></span>";
                    $sHTMLValue = '';
                    $bVertical = $sDisplayStyle != 'radio_horizontal';
                    $bExtensions = false;
                    $oAllowedValues->Rewind();
                    $aAllowedValues = array();
                    while ($oObj = $oAllowedValues->Fetch()) {
                        $aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
                    }
                    $sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", $bMandatory, $bVertical, $sValidationField);
                    $aEventsList[] = 'change';
                    break;
                case 'select':
                case 'list':
                default:
                    $sSelectMode = 'true';
                    $sHelpText = '';
                    //$this->oAttDef->GetHelpOnEdition();
                    if ($this->bSearchMode) {
                        if ($bSearchMultiple) {
                            $sHTMLValue = "<select class=\"multiselect\" multiple title=\"{$sHelpText}\" name=\"{$sAttrFieldPrefix}{$sFieldName}[]\" id=\"{$this->iId}\">\n";
                        } else {
                            $sHTMLValue = "<select title=\"{$sHelpText}\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"{$this->iId}\">\n";
                            $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
                            $sHTMLValue .= "<option value=\"\">{$sDisplayValue}</option>\n";
                        }
                    } else {
                        $sHTMLValue = "<select title=\"{$sHelpText}\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"{$this->iId}\">\n";
                        $sHTMLValue .= "<option value=\"\">" . Dict::S('UI:SelectOne') . "</option>\n";
                    }
                    $oAllowedValues->Rewind();
                    while ($oObj = $oAllowedValues->Fetch()) {
                        $key = $oObj->GetKey();
                        $display_value = $oObj->GetName();
                        if ($oAllowedValues->Count() == 1 && $bMandatory == 'true') {
                            // When there is only once choice, select it by default
                            $sSelected = ' selected';
                        } else {
                            $sSelected = is_array($value) && in_array($key, $value) || $value == $key ? ' selected' : '';
                        }
                        $sHTMLValue .= "<option value=\"{$key}\"{$sSelected}>{$display_value}</option>\n";
                    }
                    $sHTMLValue .= "</select>\n";
                    if ($this->bSearchMode && $bSearchMultiple) {
                        $aOptions = array('header' => true, 'checkAllText' => Dict::S('UI:SearchValue:CheckAll'), 'uncheckAllText' => Dict::S('UI:SearchValue:UncheckAll'), 'noneSelectedText' => Dict::S('UI:SearchValue:Any'), 'selectedText' => Dict::S('UI:SearchValue:NbSelected'), 'selectedList' => 1);
                        $sJSOptions = json_encode($aOptions);
                        $oPage->add_ready_script("\$('.multiselect').multiselect({$sJSOptions});");
                    }
                    $oPage->add_ready_script(<<<EOF
\t\toACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '{$sFilter}', '{$sTitle}', true, {$sWizHelper}, '{$this->sAttCode}', {$sJSSearchMode});
\t\toACWidget_{$this->iId}.emptyHtml = "<div style=\\"background: #fff; border:0; text-align:center; vertical-align:middle;\\"><p>{$sMessage}</p></div>";
\t\t\$('#{$this->iId}').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
\t\t\$('#{$this->iId}').bind('change', function() { \$(this).trigger('extkeychange') } );

EOF
);
            }
            // Switch
        } else {
            // Too many choices, use an autocomplete
            $sSelectMode = 'false';
            // Check that the given value is allowed
            $oSearch = $oAllowedValues->GetFilter();
            $oSearch->AddCondition('id', $value);
            $oSet = new DBObjectSet($oSearch);
            if ($oSet->Count() == 0) {
                $value = null;
            }
            if (is_null($value) || $value == 0) {
                $sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
            } else {
                $sDisplayValue = $this->GetObjectName($value);
            }
            $iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3;
            //@@@ $this->oAttDef->GetMinAutoCompleteChars();
            $iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 30;
            //@@@ $this->oAttDef->GetMaxSize();
            // the input for the auto-complete
            $sHTMLValue = "<input count=\"" . $oAllowedValues->Count() . "\" type=\"text\" id=\"label_{$this->iId}\" size=\"{$iFieldSize}\" value=\"{$sDisplayValue}\"/>&nbsp;";
            $sHTMLValue .= "<img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif\" onClick=\"oACWidget_{$this->iId}.Search();\"/>&nbsp;";
            // another hidden input to store & pass the object's Id
            $sHTMLValue .= "<input type=\"hidden\" id=\"{$this->iId}\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"" . htmlentities($value, ENT_QUOTES, 'UTF-8') . "\" />\n";
            $JSSearchMode = $this->bSearchMode ? 'true' : 'false';
            // Scripts to start the autocomplete and bind some events to it
            $oPage->add_ready_script(<<<EOF
\t\toACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '{$sFilter}', '{$sTitle}', false, {$sWizHelper}, '{$this->sAttCode}', {$sJSSearchMode});
\t\toACWidget_{$this->iId}.emptyHtml = "<div style=\\"background: #fff; border:0; text-align:center; vertical-align:middle;\\"><p>{$sMessage}</p></div>";
\t\t\$('#label_{$this->iId}').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'{$sFilter}',bSearchMode:{$JSSearchMode}, json: function() { return {$sWizHelperJSON}; } }});
\t\t\$('#label_{$this->iId}').keyup(function() { if (\$(this).val() == '') { \$('#{$this->iId}').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
\t\t\$('#label_{$this->iId}').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
\t\t\$('#{$this->iId}').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
\t\tif (\$('#ac_dlg_{$this->iId}').length == 0)
\t\t{
\t\t\t\$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
\t\t}
EOF
);
        }
        if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false) {
            $sHTMLValue .= "<img id=\"mini_tree_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_tree.gif\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"/>&nbsp;";
            $oPage->add_ready_script(<<<EOF
\t\t\tif (\$('#ac_tree_{$this->iId}').length == 0)
\t\t\t{
\t\t\t\t\$('body').append('<div id="ac_tree_{$this->iId}"></div>');
\t\t\t}\t\t
EOF
);
        }
        if ($bCreate && $bExtensions) {
            $sHTMLValue .= "<img id=\"mini_add_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_add.gif\" onClick=\"oACWidget_{$this->iId}.CreateObject();\"/>&nbsp;";
            $oPage->add_ready_script(<<<EOF
\t\tif (\$('#ajax_{$this->iId}').length == 0)
\t\t{
\t\t\t\$('body').append('<div id="ajax_{$this->iId}"></div>');
\t\t}
EOF
);
        }
        if ($sDisplayStyle == 'select' || $sDisplayStyle == 'list') {
            $sHTMLValue .= "<span id=\"v_{$this->iId}\"></span>";
        }
        $sHTMLValue .= "</span>";
        // end of no wrap
        return $sHTMLValue;
    }