Difference between revisions of "MediaWiki:ExpansionSelector.js"

From Wikicarpedia
Jump to navigation Jump to search
m (Moved OperatorLookup)
m (Add logic fo Scoring Pages top scrolling)
Line 2,602: Line 2,602:


   doEndUpdate();
   doEndUpdate();
}
//
// Adjustment for Scoring pages
//
function updateHTMLScoringToTop() {
    var height = 0;
    var obj = $('#breadcrumbs');
    if (obj.length>0) {
        height+=obj.height()*1;
    }
    var obj = $('#summary_table');
    if (obj.length>0) {
        height+=obj.height()*1;
    }
    $('html').css('scroll-padding-top',height+'px');
}
}


Line 2,670: Line 2,687:
   // Initialize preselected expansions
   // Initialize preselected expansions
   doInitPreselectedExpansions( true );
   doInitPreselectedExpansions( true );
  // Adjust top scrolling for Scoring pages
  updateHTMLScoringToTop();


   UpdateStatus.initializing = false;
   UpdateStatus.initializing = false;

Revision as of 01:02, 21 March 2024

//
// Expansion Selector organized in several tabs
//


// Constraints for Preferences
var PreferencesConstraintsArray =
[
  // At least C1 or C2 or WE have to be selected (there may be on Edition selected at least) 
  function ( oNewVisibility, oPreferencesMap ) 
  { 
    return ( oNewVisibility.C1 || oNewVisibility.C2 || oNewVisibility.WE ); 
  },

  // Validate that the fixed edition, if any, is selected
  function ( oNewVisibility, oPreferencesMap ) 
  { 
    var strFixedEdition = oPreferencesMap.fixedEdition;
    var bRes = ( strFixedEdition ? oNewVisibility[ strFixedEdition ] : true ); 
    return ( bRes ); 
  }  
  
];


// Open and close buttons
var BUTTON_OPEN = "[+]",
    BUTTON_CLOSE = "[–]";

// Constants for hooks in HTML
var EXPANSION_SELECTOR_ID = "_Expansion_Selector_",
    EXPANSION_RESULT_ID = "_Expansion_Result_",
    EXPANSION_RESULT_LIST_ID = "_Expansion_Result_List_",
    EXPANSION_COUNTER_ID = "_Expansion_Counter_",
    EXPANSION_FILTER_ID = "_Expansion_Filter_",
    SHOW_WHEN_DONE_CLASS = "showWhenDone",
    HIDE_WHEN_DONE_CLASS = "hideWhenDone",
//    CITE_ID_PREFIX = "cite_note-",
    TABS_CONTAINER_ID = "_Tabs_Container_",
    TABS_ID = "_Tabs_";


// HTML IDs that serve as anchor to insert checkboxes strings for each group
var SELECTOR_ID = 
[ 
  EXPANSION_SELECTOR_ID + "Major_",
  EXPANSION_SELECTOR_ID + "Minor_",
//  EXPANSION_SELECTOR_ID + "Licensed_",
  EXPANSION_SELECTOR_ID + "Other_",
  EXPANSION_SELECTOR_ID + "Fan_",
];    

// HTML IDs that serve as anchor to insert result strings for each group
var RESULT_ID = 
[ 
  EXPANSION_RESULT_ID + "Major_",
  EXPANSION_RESULT_ID + "Minor_",
//  EXPANSION_RESULT_ID + "Licensed_",
  EXPANSION_RESULT_ID + "Other_",
  EXPANSION_RESULT_ID + "Fan_",
];    

// Classes controlling visibility when some expansions are selected in a group
var RESULT_SELECTED_EXPANSIONS_CLASS = 
[ 
  "showMajorExpansions",
  "showMinorExpansions",
//  "showLicensedExpansions",
  "showOtherExpansions",
  "showFanExpansions",
];    

// Classes controlling visibility when no expansions are selected in a group
var RESULT_NO_EXPANSIONS_CLASS = 
[ 
  "showNoMajorExpansions",
  "showNoMinorExpansions",
//  "showNoLicensedExpansions",
  "showNoOtherExpansions",
  "showNoFanExpansions",
];    


var SELECTOR_COUNTER_ID = 
[ 
  EXPANSION_COUNTER_ID + "Major_",
  EXPANSION_COUNTER_ID + "Minor_",
//  EXPANSION_COUNTER_ID + "Licensed_",
  EXPANSION_COUNTER_ID + "Other_",
  EXPANSION_COUNTER_ID + "Fan_",
];    

//
// Quick Selector Fields
//
var QUICK_SELECT_LIST = "quickSelectList";
var QUICK_SELECT_BUTTON = "quickSelectButton";
var QUICK_CLEAR_ALL_BUTTON = "quickClearAllButton";
var QUICK_SELECT_ALL_BUTTON = "quickSelectAllButton";



// Map with input fields by Id
var InputFieldMap = {};

// Map with class names (original + derived)
var ClassNameMap = {};

var QuickSelectorMap = {};
var QuickSelectorArray = [];


//===================================================================================
//
// Translation management - Allows the translation of labels that need localization
//
//===================================================================================

// Map to store translation strings
//
var LanguageMap = {};

// Get translated text for a give id. If no value available, returns default value 
//
function getLabel( strId, strDefaultText )
{
  var strRes = strDefaultText || "";
  var strText = LanguageMap[ strId ];
  if ( strText )
  {
    strRes = strText;
  }
  
  return strRes;
}

// Populates LanguageMap with all the language strings included in the contents of
// div#wica-translation-table > div[id]
//
function doInitLanguageMap()
{
  var oHTMLDIV = $( "div#wica-translation-table > div" );
  oHTMLDIV.each(
    function () {
      var strId = $( this ).attr('id');
      var strHTML = $( this ).html();

      var bFanExpansion = $( this ).hasClass( "addFanExpansion" );
      if ( bFanExpansion )
      {
        var oExp = /^input(.*)_Label$/;
        var arrExp = oExp.exec( strId );
        var strSelector = arrExp[1];
        if ( strSelector )
        {
          var strInput = "input" + strSelector;
          var strClassName = "show" + strSelector;
          var bHasC1 = $( this ).hasClass( "showC1" );
          var bHasC2 = $( this ).hasClass( "showC2" );
          var bHasWE = $( this ).hasClass( "showWE" );
          var arrEditions = [];
          if ( bHasC1 )
          {
            arrEditions.push( "C1" );
          }
          if ( bHasC2 )
          {
            arrEditions.push( "C2" );
          }
          if ( bHasWE )
          {
            arrEditions.push( "WE" );
          }

          ItemTree.push( {
            label: strHTML,
            inputFieldId: strInput,
            className: strClassName,
            edition: arrEditions,
            group: GROUP_FAN,
            selected: false
          } );
          
        }
      }
    
      LanguageMap[ strId ] = strHTML;
    }
  );
  
  // Processes DIV/SPAN/A/B tags with class "wica-translate-contents"
  // Their contents are replaced by their translated equivalent
  //
  var oHTMLItem = $( ".wica-translate-contents" );
  oHTMLItem.each(
    function () {
      var strId = $( this ).attr('id');
      var strDefaultText = $( this ).html();
      var strTranslatedText = getLabel( strId, strDefaultText );
      
      $( this ).html( strTranslatedText );
    }
  );

  // Processes INPUT tags with class "wica-translate-attr-value"
  // Their value property is replaced by its translated equivalent
  //
  var oHTMLItem = $( "input.wica-translate-attr-value" );
  oHTMLItem.each(
    function () {
      var strId = $( this ).attr('id');
      var strDefaultText = $( this ).prop( 'value' );
      var strTranslatedText = getLabel( strId, strDefaultText );
      
      $( this ).prop( 'value', strTranslatedText );
    }
  );

  // Removes the translation table
  $( "div#wica-translation-table" ).html( "" );
}



//===================================================================================
//
// Update management - Refreshes internal status and L&F  depending on various events
//
//===================================================================================

var EVENT_EXPANSION_CHANGE = 0,
    EVENT_FILTER_CHANGE = 1,
    EVENT_EDITION_CHANGE = 2,
    EVENT_MARK_ADAPTATION_CHANGE = 3;

var UpdateStatus = 
{
  initializing: true,
  counter: 0,
  events: {}
};

// Increments counter of nested updates
//
function doStartUpdate()
{
  UpdateStatus.counter++;
}

// Decrements counter of nested updates. It if reaches 0 then runs all requested updates
//
function doEndUpdate()
{
  UpdateStatus.counter--;
  
  // Only 
  if ( UpdateStatus.counter == 0 )
  {
    var oEvents = UpdateStatus.events;
    
    var bUpdateExpansions = oEvents[ EVENT_EXPANSION_CHANGE ] || false;
    var bUpdateFilter = oEvents[ EVENT_FILTER_CHANGE ] || false;
    var bUpdateEdition = oEvents[ EVENT_EDITION_CHANGE ] || false;
    var bUpdateMark = oEvents[ EVENT_MARK_ADAPTATION_CHANGE ] || false; 
    
    // Update selected filters tab label (filter + edition), if necessary
    if ( bUpdateFilter || bUpdateEdition || bUpdateMark )
    {
      doUpdateFilterLabel();
    }

    // Updates the elements visibility according to preferred edition
    if ( bUpdateEdition )
    {
      doUpdatePreferredEdition();
    }
    
    // Re-evaluate derived classes if any change in expansions or in preferred edition
    if ( bUpdateExpansions || bUpdateEdition )
    {
      doEvalDerivedClasses();
    }

    if ( bUpdateFilter )
    {
      doUpdateSelectedEdition();
    }

    if ( bUpdateExpansions )
    {
      doUpdateAdditionalStyles();
      doUpdateFootnotes();
    }
    
    // Updates selection expansion lists and counters, if necessary
    if ( bUpdateExpansions || bUpdateFilter )
    {
      doUpdateResultListSeparators();
      doUpdateCounters();
    }

    // Updates rules adaptation marks
    if ( bUpdateMark || bUpdateEdition )
    {
      doUpdateAdaptationMarks();
    }
    
    // Updates the cookie with the current selection
    if ( bUpdateExpansions || bUpdateFilter || bUpdateEdition || bUpdateMark )
    {
      doRecordStatus();
    }

    // Resets triggered events
    UpdateStatus.events = {};
  }
}

// Marks an event for update
//
function doNotifyUpdateEvent ( iEvent1 /*, iEvent2,... */ )
{
  var n = arguments.length;
  for ( var i = 0; i < n; i++ )
  {
    var iEvent = arguments[ i ];
    UpdateStatus.events[ iEvent ] = true;
  }
}


//===================================================================================
//
// Footnote management - Manages conditional footnote visibility
//
//===================================================================================


var FootnoteTree = [];
//
// [
//   // One entry per references group
//   [
//     // One object entry per foornote 
//     {
//       className: showXXX,
//       backLinkBlocks:         // null for single backlinks 
//       { 
//         nodeSingle: ref to synth TAG /span.mw-cite-backlink.wica-single-backlink/
//         nodeSingleRef: ref to synth TAG /span.mw-cite-backlink.wica-single-backlink a/
//         nodeMulti: ref to synth TAG /span.mw-cite-backlink.wica-multiple-backlink/
//       },
//       backLinks: [
//         // One entry per backlink
//         {
//           className: showYYYY
//           nodeLabel: ref to /sup a [text node]/ OR null (if 1 back link only)
//           nodeRef: ref to TAG /sup.reference a/
//           nodeRefId: href to TAG /sup/
//         },
//         ...
//       ]
//     },
//     ...
//   ],
//   ...
// ]

// Creates data structure to manage footnote conditional visibility and renumbering
//
function doInitFootnotes()
{
    FootnoteTree = [];
    
    // Get sample text for single back links
    var oAHTML = $( "ol.references li span.mw-cite-backlink a span.cite-accessibility-label" ).first();
    var strSingleBackLinkLabel = "";
    
    if ( oAHTML.length )
    {
      strSingleBackLinkLabel = oAHTML.text();
    }
    
    var oSynthBackLinkHTMLModel = $( '<span class="mw-cite-backlink wica-single-backlink"><a href="#cite_ref-DUMMY"><span class="cite-accessibility-label">' + strSingleBackLinkLabel + '</span>↑</a></span>' );
    
    var oAccessibilityLabelModel = $( '<span class="cite-accessibility-label">Jump up to: </span>' );
    
    // Create data structure to support footnote renumbering
    var oOlHTML = $( "ol.references" );
    oOlHTML.each(
      function ( iOlIndex )
      {
        var arrReferenceGroup = [];
      
        var oLiHTML = $( this ).children( "li" );
        oLiHTML.each (  

          function ( iIndex ) 
          {
            var oParentLiHTML = $( this );
            
            var oFootnoteItem = 
            { 
              className: null,
              backLinkBlocks: null
            };
            
            var oSpanTextHTML = oParentLiHTML.children( "span.reference-text" );
            
            var arrClassNames = [];

            var oSpanConditionHTML = oSpanTextHTML.children ( "span.showFootnote" );
            if ( oSpanConditionHTML.length )
            {
              // This footnote is conditional

              // Split classes and prepare info for backlinks processing
              var strClassValue = oSpanConditionHTML.attr( "class" );
              
              var arrClassNames = strClassValue.split( " " );
              var iCount = arrClassNames.length;
              
              var strFootnoteClassName = arrClassNames.shift();
              
              if ( strFootnoteClassName === "showFootnote" )
              {
                strFootnoteClassName = arrClassNames.shift();

                oParentLiHTML.addClass( strFootnoteClassName );     // Adds class to LI tag
                oSpanConditionHTML.removeClass( strFootnoteClassName );  // Remove class from current SPAN tag
                
                // Add className to Footnote info
                oFootnoteItem.className = strFootnoteClassName;
              }
            }
            
            var oBackLinksHTML = oParentLiHTML.children( "span.mw-cite-backlink" );

            var oSupHTML = oBackLinksHTML.children( "sup" );
            if ( oSupHTML.length )
            {
              // Process nested backlinks by SUP tags
             
              oBackLinksHTML.addClass( "wica-multiple-backlink" );
              var oSynthBackLinkHTML = oSynthBackLinkHTMLModel.clone().css( "display", "none" ).insertBefore( oBackLinksHTML );
              
              var oSynthBackLinkAHTML = oSynthBackLinkHTML.children( "a" );
              
              oFootnoteItem.backLinkBlocks =
              {
                nodeSingle: oSynthBackLinkHTML,
                nodeSingleRef: oSynthBackLinkAHTML,
                nodeMulti: oBackLinksHTML
              };
              
              var arrBackLinkInfo = [];
/*                  
    <sup>
      <a href="#cite_ref-ecclesiastical-building_3-0">
        <span class="cite-accessibility-label">Jump up to: </span>3.0
      </a>
    </sup> 
    <sup>
      <a href="#cite_ref-ecclesiastical-building_3-1">3.1</a>
    </sup> 
*/
              oSupHTML.each(
                function ( iBackLinkIndex )
                {
                  var strBackLinkClassName = null;
                
                  var oParentSupHTML = $( this );
                  var strBackLinkClassNameTmp = arrClassNames[ iBackLinkIndex ];
                  if ( strBackLinkClassNameTmp )
                  {
                    var arrClassNameParts = strBackLinkClassNameTmp.split( "_" );
                    if ( arrClassNameParts.length == 2 )
                    {
                      strBackLinkClassName = arrClassNameParts[ 0 ];
                      var strBackLinkNumber = +(arrClassNameParts[ 1 ]);
                      
                      if ( strBackLinkNumber == iBackLinkIndex )
                      {
                        oParentSupHTML.addClass( strBackLinkClassName );
                        oSpanConditionHTML.removeClass( strBackLinkClassNameTmp );
                      }
                    }
                  }
                  
                  // Process span[href="#backlinkId"] span.cite-accessibility-label + text(X.Y) for iBackLinkIndex == 0
                  // Process span[href="#backlinkId"] text(X.Y) for iBackLinkIndex > 0
                  
                  var oAHTML = oParentSupHTML.find( "a" ).first();
                  var strCallId = oAHTML.attr( "href" );
                  
                  var oAccessibilityLabel = null;
                  
                  if ( iBackLinkIndex > 0)
                  {
                    oAccessibilityLabel = oAccessibilityLabelModel.clone().prependTo( oAHTML );
                  }
                  else
                  {
                    oAccessibilityLabel = oAHTML.children( "span.cite-accessibility-label" );
                  }

                  var oTextNode = oAHTML.contents().last().filter(
                    function ()
                    {
                      return this.nodeType === Node.TEXT_NODE;
                    }
                  ); 
                                    
                  var oCallHTML = $( document ).find( "sup.reference" + strCallId + " a");
//                oCallHTML.text( "[" + iFootnoteIndex + "]" );

                  // Add backlink info to footnote data
                  var oBackLinkInfo = {
                    className: strBackLinkClassName,
                    nodeLabel: oTextNode,
                    nodeRef: oCallHTML,
                    nodeRefId: strCallId,
                    nodeAccessibilitySpan: oAccessibilityLabel
                  };
                  arrBackLinkInfo.push( oBackLinkInfo );
                }
              );
              
              // Add backlinks to footnote info
              oFootnoteItem.backLinks = arrBackLinkInfo;
            }
            else
            {
              // Process span[href="#backlinkId"]
              var oAHTML = oParentLiHTML.find( "a" ).first();
              var strCallId = oAHTML.attr( "href" );
              
              var oCallHTML = $( document ).find( "sup.reference" + strCallId + " a");
              
//              oCallHTML.text( "[" + iFootnoteIndex + "]" );

              // Check if there ia an associated className to the back link
              var strBackLinkClassName = null;
/*              
              var strBackLinkClassNameTmp = arrClassNames[ 0 ];
              if ( strBackLinkClassNameTmp )
              {
                var arrClassNameParts = strBackLinkClassNameTmp.split( "_" );
                if ( arrClassNameParts.length == 2 )
                {
                  strBackLinkClassName = arrClassNameParts[ 0 ];
                  var strBackLinkNumber = +(arrClassNameParts[ 1 ]);
                      
                  if ( strBackLinkNumber == 0 )
                  {
                    oParentSupHTML.addClass( strBackLinkClassName );
                    oSpanConditionHTML.removeClass( strBackLinkClassNameTmp );
                  }
                }
              }
*/
              // Add backlink info to footnote data
              var oBackLinkInfo = {
                className: strBackLinkClassName,
                nodeLabel: null,              // Not needed
                nodeRef: oCallHTML,
                nodeRefId: null,              // Not needed
                nodeAccessibilitySpan: null   // Not needed
              };
              oFootnoteItem.backLinks = [ oBackLinkInfo ];
            }
            
            arrReferenceGroup.push( oFootnoteItem );
                
          } // End function ( li )
        );
        
        FootnoteTree.push( arrReferenceGroup );
      } // End function ( ol.references )
    );
}

// Updates footnote visibility and numbering
//
function doUpdateFootnotes()
{
  var nFootnoteGroups = FootnoteTree.length;
  for ( var i = 0; i < nFootnoteGroups; i++ )
  {
    var iIndexFootnote = 1;
    
    var arrFootnotes = FootnoteTree[ i ];
    var nFootnotes = arrFootnotes.length;
    for ( var j = 0; j < nFootnotes; j++ )
    {
      var oFootnote = arrFootnotes[ j ];
     
      var strFootnoteClassName =  oFootnote.className;
      var bFootnoteVisible = strFootnoteClassName ? doEval( strFootnoteClassName ) : true;      
      
      if ( !bFootnoteVisible ) continue;
      
      var iIndexBackLink = 0;
      var iIndexVisibleBackLink = -1;
      
      var arrBackLinks = oFootnote.backLinks;
      var nBackLinks = arrBackLinks.length;
      for ( var k = 0; k < nBackLinks; k++ )
      {
        var oBackLink = arrBackLinks[ k ];
        
        var strBackLinkClassName = oBackLink.className;
        var bBackLinkVisible = strBackLinkClassName ? doEval( strBackLinkClassName ) : true;
        
        if ( !bBackLinkVisible ) continue;

        var oBackLinkLabelHTML = oBackLink.nodeLabel;
        
        iIndexVisibleBackLink = k;
        
        if ( oBackLinkLabelHTML )
        {
          var strBackLinklLabel = "" + iIndexFootnote + "." + iIndexBackLink;
          oBackLinkLabelHTML[0].textContent = ( strBackLinklLabel );
        }
        
        var oBackLinkRefHTML = oBackLink.nodeRef;
        var strText = oBackLinkRefHTML.text();
        var iBlank = strText.indexOf( " " );
        if ( iBlank < 0 )
        {
          oBackLinkRefHTML.text( "[" + iIndexFootnote + "]" );
        }
        else
        {
          var strTextUpdated = strText.replace( /\s[^\]]+/, " " + iIndexFootnote );
          oBackLinkRefHTML.text( strTextUpdated );
        }

        iIndexBackLink++;
      }
      
      if ( nBackLinks > 1 )
      {
        var oBackLinkBlocks = oFootnote.backLinkBlocks;
        var oNodeSpanSingleHTML = oBackLinkBlocks.nodeSingle;
        var oNodeSpanSingleRefHTML = oBackLinkBlocks.nodeSingleRef;
        var oNodeSpanMultipleHTML = oBackLinkBlocks.nodeMulti;

        if ( iIndexBackLink == 1 )
        {
          var oBackLink = arrBackLinks[ iIndexVisibleBackLink ];
          var strCallId = oBackLink.nodeRefId;
          
          oNodeSpanSingleHTML.show();
          oNodeSpanMultipleHTML.hide();
          oNodeSpanSingleRefHTML.attr( "href", strCallId );
        }
        else
        {
          oNodeSpanSingleHTML.hide();
          oNodeSpanMultipleHTML.show();
        }
      }
            
      iIndexFootnote++;
    }
  }
}




//===================================================================================
//
// Serialization management - Stores current selection in cookies
//
//===================================================================================

// Encodes state in a string of the format #+C1#*C2#-WE#M#!#name1#-name2#+name3# 
//    Receives preferences map in oPreference Map and classNames status in oTreeMap
//
function doEncodeStatus( oPreferenceMap, oTreeMap )
{
  var strRes = "#";

  //
  // Encode Preferences Map
  //
  var oVisibility = oPreferenceMap.show;
  var strPrefer = oPreferenceMap.prefer;
  var bMark = oPreferenceMap.mark;
  for ( var strEdition in oVisibility )
  {
    var bSelected = oVisibility[ strEdition ];
    
    var strSelected = "";
    if ( bSelected ) 
    {
      if ( strEdition === strPrefer )
      {
        strSelected = "*"; // Selected and preferred
      }
      else
      {
        strSelected = "+"; // Selected only
      }
    }
    else
    {
      strSelected = "-"; // Not selected
    }
    
    var strItemRes = strSelected + strEdition;
    strRes += strItemRes + "#";
  }

  // Encode mark
  if ( bMark )
  {
    strRes += "M#";
  }

  //
  // Encode Classes Map: with the format
  //
  strRes += "!#";
  
  for ( var strKey in oTreeMap )
  {
    var oTreeItem = oTreeMap[ strKey ];
    
    // Skip derived classes
    if ( oTreeItem.expression )
    { 
      continue;
    }
    
    var bSelected = oTreeItem.selected || false;
    if ( bSelected )
    {
      var strOpen = "";
      
      var oItemParent = oTreeItem.parent;
      if ( oItemParent )
      {
        var bOpen = oItemParent.open;
        strOpen = ( bOpen === true ? "+" : "-" );
      }
            
      var strItemRes = strOpen + strKey;
      strRes += strItemRes + "#";
    }    
  }
  
  return strRes;
}


// Decodes state given in strInfo of the format #+C1#*C2#-WE#|#name1#-name2#+name3# 
//    Receives output preferences map in oPreference Map and classNames status in oTreeMap
//
function doDecodeStatus( oPreferenceMap, oTreeMap, strInfo )
{
  var bOK = false;
  
  var strData = strInfo || "";
  var arrParts = strData.split ( "!" );
  
  // If no entry string exists with error
  bOK = ( arrParts.length == 2 );
  if ( !bOK ) 
  {
    return bOK;
  }
  
  //
  // Process info related to Preferences
  //
  var strPreferenceData = arrParts[ 0 ];
  var arrPrefencesList = strPreferenceData.split( "#" );  
  var n = arrPrefencesList.length - 1;
  
  bOK = ( ( n > 0 ) &&
          ( arrPrefencesList[ 0 ] === "" ) && 
          ( arrPrefencesList[ n ] === "" ) );
  if ( !bOK )
  {
    return bOK;
  }
  
  // If not initialized, reset preferences map
  if ( !oPreferenceMap.show )
  {
    oPreferenceMap.show = {};
    oPreferenceMap.prefer = "";
  }

  oPreferenceMap.mark = false;
  
  for ( var i = 1; i < n; i++ )  // entries 0 and n-1 have to be empty ("")
  {
    var strEntry = arrPrefencesList[ i ];
    var strPrefix = strEntry.charAt( 0 );
    var strEdition = strEntry.substr( 1 );
    
    var bValue = ( ( strPrefix == "+" ) || ( strPrefix == "*" ) ) ; 
    var bPrefer = ( strPrefix == "*" ); 
    var bMark = ( strPrefix == "M" );

    // If the prefix is M, then it is the last entry - it is optional
    if ( bMark )
    {
      oPreferenceMap.mark = bMark;
      break;
    }
    
    oPreferenceMap.show[ strEdition ] = bValue;
    if ( bPrefer )
    {
      oPreferenceMap.prefer = strEdition;
    }
  }
  
  //
  // Load class names
  //
  var strClassNamesData = arrParts[ 1 ];
  var strClassNamesList = strClassNamesData.split( "#" );  
  var n = strClassNamesList.length - 1;
  
  bOK = ( ( n > 0 ) &&
          ( strClassNamesList[ 0 ] === "" ) && 
          ( strClassNamesList[ n ] === "" ) );
  if ( !bOK )
  {
    return bOK;
  }
 
  //
  // Decodes array and creates intermediate info structure
  //
  var oLoadedInfo = {};
  
  for ( var i = 1; i < n; i++ )  // entries 0 and n-1 have to be empty ("")
  {
    var strEntry = strClassNamesList[ i ];
    var strPrefix = strEntry.charAt( 0 );
    var bOpen = false;
    
    if ( strPrefix == "+" )
    {
      strEntry = strEntry.substr ( 1 );
      bOpen = true;
    }
    else if ( strPrefix == "-" )
    {
      strEntry = strEntry.substr ( 1 );
    }

    oLoadedInfo[ strEntry ] = { open: bOpen, selected: true };
  }

  //
  // Resets class names (all classes and all nested groups) according to loaded info
  //
  for ( var strKey in oTreeMap )
  {
    var oEntry = oTreeMap[ strKey ];
    var oInfo = oLoadedInfo[ strKey ];

    var oHTMLCheck = $( "#" + oEntry.inputFieldId );
    var bOldSelected = oEntry.selected;
    
    var bNewSelected = ( oInfo ? oInfo.selected : false );
   
    // Toggles checkbox if needed
    if ( bOldSelected != bNewSelected )
    {
      oHTMLCheck.click();  
    }

    // Toggles parent group if needed
    var oParentEntry = oEntry.parent;
    if ( oParentEntry && oInfo && ( oParentEntry.open != oInfo.open ) )
    {
       var oHTMLLink = $( "a#" + oParentEntry.inputFieldId );
       doToggle( oHTMLLink );
    }
  }
  
  return bOK;
}


var COOKIE_PREFIX = "WICA_",
    COOKIE_NAME = "SELECTOR",
    COOKIE_PATH = "/",
    COOKIE_EXPIRES = null; // 365 * 24 * 3600 * 100;
    
var USE_LOCAL_STORAGE = false; // ( typeof( Storage ) !== "undefined"  );

function doStoreInfo( strValue, strCookiePrefix )
{
  if ( USE_LOCAL_STORAGE )
  {
    sessionStorage.setItem( strCookiePrefix + COOKIE_NAME, strValue );
  }
  else
  {
    mw.cookie.set( COOKIE_NAME, strValue, { path: COOKIE_PATH, expires: COOKIE_EXPIRES, prefix: strCookiePrefix } );
  }
}

function doRestoreInfo( strCookiePrefix )
{
  var strValue = "";

  if ( USE_LOCAL_STORAGE )
  {
    strValue = sessionStorage.getItem( strCookiePrefix + COOKIE_NAME );
  }
  else
  {
    strValue = mw.cookie.get( COOKIE_NAME, strCookiePrefix );
  }

  return strValue;
}

function doRecordStatus()
{
  var strStatus = doEncodeStatus( PreferencesMap, ClassNameMap );
  var strFixedEdition = PreferencesMap.fixedEdition;
  var strCookiePrefix = ( strFixedEdition ? ( COOKIE_PREFIX + strFixedEdition + "_" ) : COOKIE_PREFIX );
  //mw.cookie.set( COOKIE_NAME, strStatus, { path: COOKIE_PATH, expires: COOKIE_EXPIRES, prefix: strCookiePrefix } );
  doStoreInfo( strStatus, strCookiePrefix );
}

function doRestoreStatus()
{
  var strFixedEdition = PreferencesMap.fixedEdition;
  var strCookiePrefix = ( strFixedEdition ? ( COOKIE_PREFIX + strFixedEdition + "_" ) : COOKIE_PREFIX );
  //var strStatus = mw.cookie.get( COOKIE_NAME, strCookiePrefix );
  var strStatus = doRestoreInfo( strCookiePrefix );
  if ( strStatus != null )
  {
    var bOK = doDecodeStatus( PreferencesMap, ClassNameMap, strStatus );
    if ( bOK )
    {    
      doStartUpdate();
      doNotifyUpdateEvent( 
        EVENT_EXPANSION_CHANGE, 
        EVENT_FILTER_CHANGE, 
        EVENT_EDITION_CHANGE,
        EVENT_MARK_ADAPTATION_CHANGE );
      doEndUpdate();    
    }
  }
}


//===================================================================================
//
// Presets management - Loading preset lists of expansions (Quick Selectors)
//
//===================================================================================


// Get URL Param
//
function getUrlParam( strParam )
{
  var oRes = ( location.search.match( new RegExp( "[?|&]" + strParam + "=(.+?)(&|$)") ) || [, null] )[1] || '';
  return oRes;
}


// Returns an array eliminating all oElements different than oElement 
//
function doFilterArray( arrElements, oElement )
{
  var arrRes = [];
  var n = arrElements.length;
  for ( var i = 0; i < n; i++ )
  {
    var o = arrElements[ i ];
    if ( o == oElement )
    {
      arrRes.push ( o );
    }
  }
  
  return arrRes;
}


// Allows to select/deselect all expansions. If an edition is specified 
// (strEditionAllowed equals to C1, C2, WE), all expansions will be 
// constrained to that edition
//
function doSelectAllExpansions( bValue, strEditionAllowed )
{
  doStartUpdate();

  var oVisibility = PreferencesMap.show;
  for (var strClassName in ClassNameMap )
  {
    var o = ClassNameMap[ strClassName ];
    
    // Only simple class names are processed
    if ( !o.expression )
    {
      var arrEdition = o.edition || [];
      var arrFilteredEdition = arrEdition;
      
      if ( strEditionAllowed )
      {
        arrFilteredEdition = doFilterArray( arrEdition, strEditionAllowed );
      }
      
      var bIsVisible = isVisible( arrFilteredEdition, oVisibility )
      if ( bIsVisible )
      {
        var oHTMLCheck = $( "#" + o.inputFieldId );
        var bOldSelected = o.selected;
        if ( bValue != bOldSelected )
        {
          oHTMLCheck.click();  
        }
      }
    }
  }

  doInitPreselectedExpansions();

  doEndUpdate();
}


// Loads an expansion preset 
//
function doLoadExpansionSet( strPresetName )
{
  doStartUpdate();

  var oVisibility = PreferencesMap.show;
  var oExpansionPreset = QuickSelectorMap[ strPresetName ];
  
  var arrExpansionPreset = oExpansionPreset.classNames; 
  var n = arrExpansionPreset.length;
  for ( var i = 0; i < n; i++ )
  {
    var strClassName = arrExpansionPreset[ i ];
    var o = ClassNameMap[ strClassName ]; 
    
    // Only simple class names are processed
    if ( !o.expression )
    {
      var arrEdition = o.edition || [];
      
      var bIsVisible = isVisible( arrEdition, oVisibility )
      if ( bIsVisible )
      {
        var oHTMLCheck = $( "#" + o.inputFieldId );
        var bOldSelected = o.selected;
        if ( bOldSelected == false )
        {
          oHTMLCheck.click();  
        }
      }
    }
  }

  doInitPreselectedExpansions();

  doEndUpdate();
}


function doQuickSelectionAction( strAction, strPresetId )
{
  if ( strAction == ACTION_CLEAR_ALL )
  {
    doSelectAllExpansions( false );
  }
  else if ( strAction == ACTION_SELECT_ALL )
  {
    doSelectAllExpansions( true );
  }
  else if ( strAction == ACTION_SELECT_ALL_C1 )
  {
    doSelectAllExpansions( true, "C1" );
  }
  else if ( strAction == ACTION_SELECT_ALL_C2 )
  {
    doSelectAllExpansions( true, "C2" );
  }
  else if ( strAction == ACTION_SELECT_ALL_WE )
  {
    doSelectAllExpansions( true, "WE" );
  }
  else if ( strAction == ACTION_LOAD_PRESET )
  {
    doLoadExpansionSet( strPresetId );
  }
}

function doWrapQuickSelectAction( oHTMLButton )
{
  var oHTMLList = $( "#" + QUICK_SELECT_LIST );
  var strListValue = oHTMLList.val();
  
  var oAction = QuickSelectorMap[ strListValue ];
  if ( oAction )
  {
    var strAction = oAction.action || ACTION_LOAD_PRESET;  
    doQuickSelectionAction( strAction, strListValue );
  }

  oHTMLList.val( "defaultSetting" );
}

// Callback to clear the current selection
//
function doWrapClearAll( oHTMLButton )
{
  doSelectAllExpansions( false );
}


// Callback to select all expansions
//
function doWrapSelectAll( oHTMLButton )
{
  doSelectAllExpansions( true );
}



// Initializes Quick Selectors Tab
//   Precond: DerivedClasses from the Result Strings have to be initialized, as
//   we are using derived classes XXX_Full as a shortcut to avoid duplicating the
//   definition of all the classes contained in each expansions
//
function doInitializeQuickSelectors()
{
  var arrSelectors = QuickSelectorDefinition;
  
  var oHTMLSelect = $( "#" + QUICK_SELECT_LIST );
  oHTMLSelect.children('option:not(:first)').remove();

  
  var nSelectors = arrSelectors.length;
  for ( var i = 0; i < nSelectors; i++ )
  {
    var oSelector = arrSelectors[ i ];
    
    var strDefaultLabel = oSelector.defaultLabel || null;
    var strLabel = oSelector.label || strDefaultLabel;
    var strId = oSelector._id;
    var bReadOnly = oSelector.readOnly || false;
    var strAction = oSelector.action || "";
        
    var oSelectorRes = shallowCopy( oSelector );
    var arrClassesRes = [];
    oSelectorRes.classNames = arrClassesRes; // New version with full className listing
    
    QuickSelectorMap[ strId ] = oSelectorRes;
    QuickSelectorArray.push( oSelectorRes );

    var arrClassNames = oSelector.classNames || [];
    var nClassNames = arrClassNames.length;
    
    for ( var j = 0 ; j < nClassNames; j++ )
    {
      var strClassName = arrClassNames[ j ];
      
      var oClassName = ClassNameMap[ strClassName ];
      var arrExpression = oClassName.expression;
      
      if ( arrExpression )
      {
        // It is an array
        var nExpressionLength = arrExpression.length;
        
        // Adds all class names skipping the first array element (an operator function)
        for ( var k = 1; k < nExpressionLength; k++ )
        {
          var strExpressionClassName = arrExpression[k];
          arrClassesRes.push( strExpressionClassName );
        }
      }
      else
      {
        // It is a class name
        
        arrClassesRes.push( strClassName );
      }
    }
    
    
    // Load Select options
    if ( bReadOnly )
    {
      var strTranslatedLabel = getLabel( strId + "_Label", strLabel );
      oHTMLSelect.append( 
          $( "<option></option>" ) 
         .attr( "value", strId)
         .text( strTranslatedLabel ) 
      );
    }
    else // Custom
    {
    }
       
  }

  $( "#" + QUICK_SELECT_BUTTON ).click( doWrapQuickSelectAction );

  $( "#" + QUICK_CLEAR_ALL_BUTTON ).click( doWrapClearAll );
  $( "#" + QUICK_SELECT_ALL_BUTTON ).click( doWrapSelectAll );

}


//===================================================================================
//
// Visibility Management - Expansion selector Tabs
//
//===================================================================================

// Recursive function that evaluates an expresion for a Derived Class
//
function doEval( oParam )
{
  var bRes = false;
  
  if ( typeof( oParam ) === "string" )
  {
    //console.log( oParam )
    var oClassEntry = ClassNameMap[ oParam ];
    if ( oClassEntry )
    {
      bRes = oClassEntry.selected;
    }
    else if ( oParam === "showC1_Graphics" ) // Synth Class Name for C1 graphics
    {
      bRes = ( PreferencesMap.prefer === "C1" );
    }
    else if ( oParam === "showC2_Graphics" ) // Synth Class Name for C2 graphics
    {
      bRes = ( PreferencesMap.prefer === "C2" );
    }
    else if ( oParam === "showWE_Graphics" ) // Synth Class Name for WE graphics
    {
      bRes = ( PreferencesMap.prefer === "WE" );
    }
    else if ( oParam === "showC1" ) // Synth Class Name for C1 or WE
    {
      bRes = ( ( PreferencesMap.prefer === "C1" ) || ( PreferencesMap.prefer === "WE" ) );
    }
    else if ( oParam === "showC2" ) // Synth Class Name for C2
    {
      bRes = ( PreferencesMap.prefer === "C2" );
    }
    else if ( oParam === "showWE" ) // Synth Class Name for C1 or WE
    {
      bRes = ( ( PreferencesMap.prefer === "C1" ) || ( PreferencesMap.prefer === "WE" ) );
    }
    else if ( oParam === "showC1_Visible" ) // Synth Class Name for C1 Visible
    {
      bRes = ( PreferencesMap.show.C1 );
    }
    else if ( oParam === "showC2_Visible" ) // Synth Class Name for C2 Visible
    {
      bRes = ( PreferencesMap.show.C2 );
    }
    else if ( oParam === "showC1OrC2_Visible" ) // Synth Class Name for C1 or C2 Visible
    {
      bRes = ( PreferencesMap.show.C1 || PreferencesMap.show.C2 );
    }
    else if ( oParam === "showWE_Visible" ) // Synth Class Name for WE Visible
    {
      bRes = ( PreferencesMap.show.WE );
    }
    else if ( oParam === "showMark" ) // Synth Class Name for Mark Checkbox setting
    {
      bRes = ( PreferencesMap.mark === true );
    }
    else if ( oParam === "showAll" ) // Synth Class Name for "true"
    {
      bRes = true;
    }
    else 
    {
      console.log( oParam + ": Unknown Class Name" );
    }
  } 
  else if ( typeof ( oParam ) === "object" )
  {
    var arrParams = oParam || [];
    var nParams = arrParams.length;

    var fnAction = arrParams[ 0 ];
    var bRes = doEval( arrParams[ 1 ] );

    if ( nParams == 2 )
    {
      // Unary Operator
      bRes = fnAction( bRes );
    }
    else if ( nParams > 2 )
    {
      // Binary Operator
      for ( var i = 2; i < nParams; i++ )
      {
        var bParam = doEval( arrParams[ i ] );
        bRes = fnAction( bRes, bParam );
      }
    }
  }
  else if ( typeof ( oParam ) === "boolean" )
  {
    bRes = oParam;
  } 
  
  return bRes;
}

// Reevaluates all derived classes and updates the visibility of the Items associated
//
function doEvalDerivedClasses()
{
  var arrDerivedClasses = DerivedClassesTable || [];
  var nDerivedClasses = arrDerivedClasses.length;
  for ( i = 0; i < nDerivedClasses; i++ )
  {
    var o = arrDerivedClasses[ i ];
    var strClassName = o.className;
    var arrExpression = o.expression;
    var bNewSelected = doEval( arrExpression );
    var bOldSelected = o.selected;
    
    if ( bNewSelected != bOldSelected )
    {
      var str = "." + strClassName;
      if ( bNewSelected )
      {
        $( str ).show();
      }
      else
      {
        $( str ).hide();
      }
      o.selected = bNewSelected;
    } 
  }
}


// This function handles the behavior of a group link (displayed as [+] when closed 
// and [-] when open): the block of nested checkboxes is shown or hidden accordingly.
//
function doToggle ( oLinkHTML )
{
  doStartUpdate();

  var strInputFieldId = oLinkHTML.attr( "id" );

  var o = InputFieldMap[ strInputFieldId ];
  if ( !o )
  {
     // Error
     console.error( "Unknown Field:" + strInputFieldId );
     return;
  }
 
  var bOpen = o.open;

  var strToggleId = "#toggle_" + strInputFieldId;

  var oToggleHTML = $( strToggleId );
  
  if ( bOpen )
  {
    oToggleHTML.slideUp();
    oLinkHTML.html( BUTTON_OPEN );
  }
  else
  {
    oToggleHTML.slideDown();
    oLinkHTML.html( BUTTON_CLOSE );
  }
  
  o.open = !bOpen;

  doNotifyUpdateEvent( EVENT_EXPANSION_CHANGE );

  doEndUpdate();
}


// This function handles the click on individual expansion checkbox: if so
// all associated HTML Items are shown/hidden accordingly
//

function doCheckboxSimple( oCheckHTML )
{
  var strInputFieldReadOnly = oCheckHTML.attr( "readonly" );
  if ( !UpdateStatus.initializing && ( strInputFieldReadOnly == "readonly" ) )
  {
    $( oCheckHTML ).prop( "checked", true );

    return false;
  }

  doStartUpdate();
  
  var strInputFieldId = oCheckHTML.attr( "id" );
  
  var o = InputFieldMap[ strInputFieldId ];
  var bSelected = o.selected;
  
  var strClassName = o.className;
  var str = '.' + strClassName;
  if ( bSelected )
  {
    $( str ).hide();
  }
  else
  {
    $( str ).show();
  }
  
  o.selected = !bSelected;
  
  if ( o.parent != null )
  {
    doCheckParent( o.parent );
  }
  
  doNotifyUpdateEvent( EVENT_EXPANSION_CHANGE );

  doEndUpdate();
}


// This function handles the click on a group checkbox: if so
// all children are selected or deselected simultaneously and all
// associated HTML Items are shown/hidden accordingly
//
function doCheckboxGroup( oCheckHTML )
{
  var strInputFieldReadOnly = oCheckHTML.attr( "readonly" );
  if ( !UpdateStatus.initializing && ( strInputFieldReadOnly == "readonly" ) )
  {
    $( oCheckHTML ).prop( "checked", true );

    return false;
  }

  doStartUpdate();
  
  var strInputFieldId = oCheckHTML.attr( "id" );
  
  var o = InputFieldMap[ strInputFieldId ];
  var bSelected = o.selected;
  
  //o.selected = !bSelected;

  var arrChildren = o.children;
  var nChildren = arrChildren.length;
  for ( var i = 0; i < nChildren; i++ )
  {
    var oChild = arrChildren[ i ];
    var bChildSelected = oChild.selected;
    var strChildInputFieldId = oChild.inputFieldId;
    
    if ( bSelected == bChildSelected )
    {
      $('#' + strChildInputFieldId ).prop( "checked", !bSelected );
      
      var strChildClassName = oChild.className;
      var str = '.' + strChildClassName;
      if ( bSelected )
      {
        $( str ).hide();
      }
      else
      {
        $( str ).show();
      }
  
      oChild.selected = !bSelected;
    }
  }
    
  o.selected = !bSelected;
  
  doNotifyUpdateEvent( EVENT_EXPANSION_CHANGE );
  
  doEndUpdate();
}

// If a nested checkbox is clicked, this functions forces its parent 
// checkbox is checked too for the sake od consistency:
// group checkboxes are checked even if not all its children are sleected
//
function doCheckParent( oToggleCheck )
{
  var arrChildren = oToggleCheck.children;
  var nChildren = arrChildren.length;
  var bNewSelected = false;
  for ( var i = 0; i < nChildren; i++ )
  {
    var oChild = arrChildren[ i ];
    bNewSelected = bNewSelected || oChild.selected;
  }
  
  var bOldSelected = oToggleCheck.selected;
  if( bOldSelected != bNewSelected )
  {
    var strInputFieldName = oToggleCheck.inputFieldId;
    $( "#" + strInputFieldName ).prop( "checked", bNewSelected );
    oToggleCheck.selected = bNewSelected;
  }
}


// Updates any additional styling dependent on classes
//
function doUpdateAdditionalStyles()
{
  // Updates visibility of double turn grey box according to showBuilder

  var oHTML = $( "div.wica-repeat-block" );
  var bBuilder = doEval( "showBuilder" );

  if ( bBuilder )
  {
    oHTML.addClass( "wica-visible" );
  }
  else
  {
    oHTML.removeClass( "wica-visible" );
  }
}


// Update (X/Y) counters on expansion labels according to:
// a. Preferences - that control what expansion groups are elegible
// b. Selected expansions
//
// X indicates no. selected expansions on a tab
// Y indicates total no. expansions on a tab
//
function doUpdateCounters()
{
  var arrCounters = new Array( GROUP_TOTAL );

  for( var i = 0; i < GROUP_TOTAL; i++ )
  {
    arrCounters[ i ] = { selectedCount: 0, totalCount: 0 };
  }
  
  var n = ItemTree.length;
  for ( var i = 0; i < n; i++ )
  {
    var o = ItemTree[ i ];

    if ( !o.separator && o.visible )
    {
      var nGroup = o.group;
      var oCounter = arrCounters[ nGroup ];
      
      oCounter.totalCount++;
      
      if ( o.selected )
      {
        oCounter.selectedCount++;
      }
    }
  }
  
  for( var i = 0; i < GROUP_TOTAL; i++ )
  {
    var oCounter = arrCounters[ i ];
    
    var strText = "(" + oCounter.selectedCount + "/" + oCounter.totalCount + ")";
    var strId = "#" + SELECTOR_COUNTER_ID[ i ];
    $( strId ).html( strText );
  } 
}

// Ancillary function to assign the right classes to display separators:
// a. All visible LI tags have class "listSeparator" (class listSeparator is added) ...
// b. except the last visible item  (class listSeparator is removed)
//
function doTraversePipeLists( nIndex )
{
  var oThis = $( this );
  var oVisibleChildren = oThis.children( "li:visible" );
  var oVisibleChildrenNotLast = oVisibleChildren.filter( ":not(:last)" );
  var oVisibleChildrenLast = oVisibleChildren.filter( ":last" );
  
  oVisibleChildrenNotLast.addClass( "listSeparator" );
  oVisibleChildrenLast.removeClass( "listSeparator" );
  
  var oVisitChildren = oVisibleChildren.children( "ul" );
  oVisitChildren.each( doTraversePipeLists );
}

function doUpdateResultListSeparators()
{
  var oHTMLLists = $( "#" + EXPANSION_RESULT_LIST_ID + " ul" );
  oHTMLLists.each( doTraversePipeLists );
}


// Initialize dynamic classes
//

function dynamicSelectorParse( arrExpression ) 
{
  var arrRes = [];
  arrExpression.forEach( function( e ) 
  {
    if ( Array.isArray( e ) ) 
    {
      var arrTemp = dynamicSelectorParse( e );
      arrRes.push( arrTemp );
    } 
    else 
    {
      var oItem = OperatorLookup[ e ];
      if ( oItem )
      {
        arrRes.push( oItem );
      }
      else
      {
        arrRes.push( e );
      }
    }
  } );

  return arrRes;
}


function doInitDynamicClasses()
{
  var iCounter = 0;
  $('*[data-dynamic-selector]').each( function() 
  {
    var strArgument = $(this).data( 'dynamic-selector' );
    var strArgumentTemp = strArgument.replaceAll( "'" , '"' );
    var oExpression = JSON.parse( strArgumentTemp );

    if ( Array.isArray( oExpression ) && ( oExpression.length > 0 ) )
    {
      var strClassName = 'showDynamicSelector' + iCounter++;
      $(this).addClass( strClassName );
      var arrParsedExpression = dynamicSelectorParse ( oExpression );

      DerivedClassesTable.push( {
        className: strClassName,
        expression: arrParsedExpression
      } );

      $(this).removeAttr('data-dynamic-selector');
    }
    else
    {
      console.log( "Invalid Expression: " + strArgument );
    }

  } );
}

// Initialize Tabs with expansion checkbox selectors
//
function doInitTabs()
{
  //
  // Initialize tabs with expansion selector checkboxes 
  //
  
  // Caches label "(Partial)"
  var strTranslatedPartialLabel = getLabel( "textPartial_Label", "(Partial)" );


  // Create empty structure to store checkbox strings
  var arrGroupRes = new Array( GROUP_TOTAL );
  for ( var i = 0; i < GROUP_TOTAL; i++ )
  {
    var strSelectedExpansionsClassName = RESULT_SELECTED_EXPANSIONS_CLASS[ i ];
    var arrSelectedExpansionsClassRule = [ OR ];
    var strNoSelectedExpansionsClassName = RESULT_NO_EXPANSIONS_CLASS[ i ];
    var arrNoSelectedExpansionsClassRule = [ NOT, strSelectedExpansionsClassName ];
    var oDerivedClassAny = 
  {
    className: strSelectedExpansionsClassName,
    expression: arrSelectedExpansionsClassRule
  };
  
  var oDerivedClassNone =
  {
    className: strNoSelectedExpansionsClassName,
    expression: arrNoSelectedExpansionsClassRule
  }; 
    
    arrGroupRes[ i ] = { 
      strInput: "", 
      strResult: "", 
      derivedClassAny: oDerivedClassAny, 
      derivedClassNone: oDerivedClassNone 
    };
  }
  
  // Compute checkbox strings
  var n = ItemTree.length;
  for (var i = 0; i < n; i++ )
  {
    var o = ItemTree[ i ];
    var strLabel = o.label;
    var strInputFieldId = o.inputFieldId;
    var bSelected= o.selected || false;
    var arrChildren = o.children || [];
    var nChildren = arrChildren.length;
    var bOpen = o.open || false;
    var strClassName = o.className || "";
    var bDisabled = o.disabled || false;
    var nGroup = o.group;
    var oGroupRes = arrGroupRes[ nGroup ];
    var arrGroupRule = oGroupRes.derivedClassAny.expression;
    var arrEdition = o.edition;

    // Manage separators with optional spacing and label
    var bSeparator = o.separator;
    if ( bSeparator )
    {
      var strInput = '<span id="show_' + strInputFieldId + '">';
      var bSpacing = !o.labelOnly;
      if ( bSpacing )
      {
        strInput += '<br />';
      }
      
      if ( strInputFieldId )
      {
        var strTranslatedLabel = getLabel( strInputFieldId + "_Label", strLabel );
        strInput += '<b>' + strTranslatedLabel + '</b><br />';
      }

      strInput += '</span>';

      oGroupRes.strInput += strInput;
      continue;
    }
    
    InputFieldMap[ strInputFieldId ] = o;
    
    if ( strClassName.length > 0 ) // nChildren == 0
    {
      ClassNameMap[ strClassName ] = o;
    }
    
    if ( !bDisabled )
    { 
      //
      // Create selector string for extension and its children if any
      //
      
      var strChecked = ( bSelected ? ' checked="checked"' : '' );
      var strItemClassName = (nChildren > 0 ? 'checkboxGroup' : 'checkboxSimple' );

      var strTranslatedLabel = getLabel( strInputFieldId + "_Label", strLabel );
    
      var strInput = '<span id="show_' + strInputFieldId + '"><input class="' + strItemClassName +'" type="checkbox" name="' + strInputFieldId + '" id="' + strInputFieldId + '"' + strChecked + '/> ' + strTranslatedLabel;
      
      var strResult = strTranslatedLabel;
      var strResultVisibilityClass = "";

      if ( nChildren > 0 )
      {
        //
        // Prepare synthetic derived classes for the group
        //
        var strClassNamePrefix = o.classNamePrefix;
        var strDerivedClassNameFull = strClassNamePrefix + "Full";
        var arrDerivedClassRuleFull = [ AND ];
        var strDerivedClassNameAny = strClassNamePrefix + "Any";
        var arrDerivedClassRuleAny = [ OR ];
        var strDerivedClassNameNone = strClassNamePrefix + "None";
        var arrDerivedClassRuleNone = [ NOT, strDerivedClassNameAny ];
        var strDerivedClassNamePartial = strClassNamePrefix + "Partial";
        var arrDerivedClassRulePartial = [ AND, strDerivedClassNameAny, [ NOT, strDerivedClassNameFull ] ];
        DerivedClassesTable.unshift(  
          {
            className: strDerivedClassNameFull,
            expression: arrDerivedClassRuleFull
          },
          {
            className: strDerivedClassNameAny,
            expression: arrDerivedClassRuleAny
          },
          {
            className: strDerivedClassNameNone,
            expression: arrDerivedClassRuleNone
          },
          {
            className: strDerivedClassNamePartial,
            expression: arrDerivedClassRulePartial
          }
        );

        // Set derived class to control result visibility for this expansion
        strResultVisibilityClass = strDerivedClassNameAny;


        //
        // Create Selector checkbox string for children
        //
        strInput += '<span class="' + strDerivedClassNamePartial + '"> <b>' + strTranslatedPartialLabel + '</b> </span>';
        strInput += ' <a class="linkToggle" id="' + strInputFieldId + '" href="javascript:void(0);">' + ( bOpen ? BUTTON_CLOSE : BUTTON_OPEN ) + '</a><br />';
        
        strResult += '<span class="' + strDerivedClassNamePartial + '"> (<ul class="commaList">';

        var strOpen = ( bOpen ? "" : " style='display:none;'"  );

        strInput += '<blockquote id="toggle_' + strInputFieldId + '"' + strOpen + '>';
        for ( var j = 0; j < nChildren; j++ )
        {
          var oChild = arrChildren[ j ];
          oChild.selected = bSelected;
          oChild.parent = o;
          oChild.edition = arrEdition;
         
          var strChildLabel = oChild.label;
          var strChildinputFieldId = oChild.inputFieldId;
          var bChildSelected = bSelected;

          // Store information related to classes
          var strClassName = oChild.className;
          if ( strClassName.length > 0 )
          {
            InputFieldMap[ strChildinputFieldId ] = oChild;
            ClassNameMap[ strClassName ] = oChild;
            
            // Add children to derived classes
            arrDerivedClassRuleFull.push( strClassName );
            arrDerivedClassRuleAny.push( strClassName );
          }

          // Translated child label
          var strTranslatedChildLabel = getLabel( strChildinputFieldId + "_Label", strChildLabel );


          // Append checkbox text for expansion element
          strInput += '<input class="checkboxSimple" type="checkbox" name="' + strChildinputFieldId + '" id="' + strChildinputFieldId + '"' + strChecked + '/> ' + strTranslatedChildLabel + '<br />';
          
          // Append text for expansion element
          strResult += '<li class="' + strClassName + '">'  + strTranslatedChildLabel + '</li>';
        }
        strInput += '</blockquote>';

        strResult += "</ul>)</span>";
      }
      else
      {
        strInput += '<br />';
        
        // Set derived class to control result visibility for this expansion
        strResultVisibilityClass = strClassName;
      }
      
      strInput += '</span>';

      
      //
      // Append computed strings to their respective groups
      //
      
      // Wrap result text in visibility class
      strResult = '<li class="' + strResultVisibilityClass + '">' + strResult + '</li>';
            
      oGroupRes.strInput += strInput;
      oGroupRes.strResult += strResult;
      
      // Add class to derived rule for result visibility
      arrGroupRule.push( strResultVisibilityClass );
    }
  }

  // Initialize expansion tabs with computed checkboxes strings and result
  for ( var i = 0; i < GROUP_TOTAL; i++ )
  {
    var oGroupRes = arrGroupRes[ i ];
    
    var strInput = oGroupRes.strInput;
    var oHTMLInput = $( "#" + SELECTOR_ID[ i ] );
    oHTMLInput.html( strInput );
    
    var strResult = oGroupRes.strResult;
    strResult = '<ul class="pipeList">' + strResult + '</ul>';
    var oHTMLResult = $( "#" + RESULT_ID[ i ] );
    oHTMLResult.html( strResult );
    
    var oRuleAny = oGroupRes.derivedClassAny;
    var oRuleNone = oGroupRes.derivedClassNone;
    DerivedClassesTable.push ( oRuleAny, oRuleNone );
  }

  doInitDynamicClasses();

  // Initialize and computes values for derived classes
  var arrDerivedClasses = DerivedClassesTable || [];
  var nDerivedClasses = arrDerivedClasses.length;
  for ( i = 0; i < nDerivedClasses; i++ )
  {
    var o = arrDerivedClasses[ i ];
    var strClassName = o.className;
    var arrExpresion = o.expression;
    var bSelected = doEval( arrExpresion );
    
    ClassNameMap[ strClassName ] = o;
    o.selected = bSelected;
  }
  
  // Initialize visibility of visible and hidden classes
  for ( var strClassName in ClassNameMap )
  {
    var o = ClassNameMap[ strClassName ];
    var oHTML = $( "." + strClassName );
    if ( o.selected )
    {
      oHTML.show();
    }
    else
    {
      oHTML.hide();
    }
  }
  
  //
  // Register callbacks
  //
  $( ".linkToggle" ).click( 
    function() { doToggle( $(this) ); } 
  );

  $( ".checkboxSimple" ).click( 
    function() { doCheckboxSimple( $(this) ); } 
  );

  $( ".checkboxGroup" ).click( 
    function() { doCheckboxGroup( $(this) ); } 
  );

  doStartUpdate();
 
  doNotifyUpdateEvent( EVENT_EXPANSION_CHANGE );
  doRestoreStatus();

  doInitPreferences();
  doInitializeQuickSelectors();

  doEndUpdate();
}  

 

//===================================================================================
//
// Filter management - Controls the preferences tab
//
//===================================================================================

// Updatess visibility for a list of class names
//
function doUpdateVisibilityForClasses( arrKeys )
{
  for ( var iKey in arrKeys )
  {
    var strClassName = arrKeys[ iKey ];
    var bVisible = doEval( strClassName );
    if ( bVisible )
    {
      $( "." + strClassName ).show();
    }
    else
    {
      $( "." + strClassName ).hide();
    }
  }  
}


// Updates visibility of images according to preferred edition 
//
function doUpdatePreferredEdition()
{
  var arrKeys = [ "showC1", "showC2", "showWE", "showC1_Graphics", "showC2_Graphics", "showWE_Graphics" ];

  doUpdateVisibilityForClasses( arrKeys );
}


// Updates visibility of texts according to selected edition 
//
function doUpdateSelectedEdition()
{
  var arrKeys = [ "showC1_Visible", "showC2_Visible", "showWE_Visible", "showC1OrC2_Visible" ];

  doUpdateVisibilityForClasses( arrKeys );
}


// Update the Label on Filter Tab to indicate what editions are visible and
// what edition is preferred (with precedence for images and rules)   
// Example: the label will list the editions "(C1/C2/...) #" underlining the preferred one
// The # sign indicates rules adaptations are highlighted  
//
function doUpdateFilterLabel()
{
  var strText = "";
  var bEmpty = true;
  
  var oVisibility = PreferencesMap.show;
  var strPrefer = PreferencesMap.prefer;
  var bMark = PreferencesMap.mark;
  for ( var strKey in oVisibility )
  {
    if ( oVisibility[ strKey ] )
    {
      if ( !bEmpty )
      {
        strText += "/";
      }

      var strTranslatedKey = getLabel( "tabFilters_" + strKey + "_Label", strKey );

      var bPrefer = ( strKey == strPrefer );
      if ( bPrefer )
      {
        strText += "<u>" + strTranslatedKey + "</u>";
      }
      else
      {
        strText += strTranslatedKey;
      }
      
      bEmpty = false;
    }
  }
  
  if ( bEmpty )
  {
    strText = "&ndash;";
  }
  
  strText = "(" + strText + ")";

  if ( bMark )
  {
    strText += " #"; 
  }
  
  var strFilterId = "#" + EXPANSION_FILTER_ID;
  $( strFilterId ).html( strText );
}


// Check the current status (given by oStatus) allows an Item to be 
// visible (arrEdition provides the editions of the Item) 
//
function isVisible( arrEdition, oStatus )
{
  var bRes = false;
  var n = arrEdition.length;
  
  for ( var i = 0; i < n; i++ )
  {
    var strEdition = arrEdition[ i ];
    var bStatus = oStatus[ strEdition ] || false;
    bRes = bRes || bStatus;
  }
  
  return bRes; 
}

// Action that updates a Prefer radiobox (identified by its edition id) according 
// to the new state (provided by bNewSelected)
//
function doPrefer( strEdition, bNewSelected )
{
  doStartUpdate();

  if ( bNewSelected )
  {
    PreferencesMap.prefer = strEdition;
    doNotifyUpdateEvent( EVENT_EDITION_CHANGE );
  }
  else
  {
    doSearchNewPrefer( strEdition );
  }
    
  doEndUpdate();
}


// Callback for Radiobuttons (Prefer)
//
function doWrapPrefer( oHTMLRadio )
{
  var strValue = oHTMLRadio.val();
  var bSelected = oHTMLRadio.prop( "checked" );
  
  doPrefer( strValue, bSelected );
}


// If currently selected Prefer radiobutton is disabled, this function tries to
// select a new Prefer radiobutton, if available
//
function doSearchNewPrefer( strAvoidEdition )
{
  doStartUpdate();

  var strNewPrefer = "";
  var oVisibility = PreferencesMap.show;
  for ( var strKey in oVisibility )
  {
    if ( strKey == strAvoidEdition ) 
    {
      continue;
    }
  
    var bVisible = oVisibility[ strKey ];
    if ( bVisible )
    {
      var strNewRadioId = "#prefer" + strKey;
      var oNewHTMLRadio = $( strNewRadioId );
      if ( oNewHTMLRadio.length )
      {
        strNewPrefer = strKey;
        oNewHTMLRadio.prop( "checked", true );
        break;
      }
    }
  }
  PreferencesMap.prefer = strNewPrefer;
  doNotifyUpdateEvent( EVENT_EDITION_CHANGE );

  doEndUpdate();
}

// Ancillary function to do a shallow copy of a Javascript object
//
function shallowCopy( oPrototype ) 
{
   var oRes = {};
   for( var strKey in oPrototype )
   {
     oRes[ strKey ] = oPrototype[ strKey ];
   } 
   
   return oRes;
}

// Highlights rules adapted for one edition to another
//
function doUpdateAdaptationMarks()
{
  var oVisibility = PreferencesMap.show;
  var strPrefer = PreferencesMap.prefer;
  var bMark = PreferencesMap.mark;
  
  // Remove all highlightings
  for ( var strEdition in oVisibility )
  {
    var strMarkClassName = ".mark" + strEdition;
    var oHTML = $( strMarkClassName );
    oHTML.removeClass( "wica-highlight" ); 
  }
  
  if ( bMark )
  {
    // Only show highlighting triggered by preferred edition
    var strMarkClassName = ".mark" + strPrefer;
    var oHTML = $( strMarkClassName );
    oHTML.addClass( "wica-highlight" ); 

    // Show elements associated to the highlighting when it is on
    $( ".showMark" ).show();
  }
  else
  {
    // Show elements associated to the highlighting when it is off
    $( ".showMark" ).hide();
  }
}


// Function that handles the highlighting of rules adaptations to other editions
//
function doMarkAdaptations( strValue, bSelected, bInit )
{
  doStartUpdate();

  var bOldSelected = PreferencesMap.mark;
  var bHasChange = ( bOldSelected != bSelected );

  if ( bHasChange || bInit )
  {
    doNotifyUpdateEvent( EVENT_MARK_ADAPTATION_CHANGE );
    
    PreferencesMap.mark = bSelected;
  }

  doEndUpdate();
}



// Function that handles a click on a Filter checkbox (strEdition indicates the
// edition Id that was clicked and bNewSelected, the new expected state). 
// It serves as initialization functions for Filter checkboxes (and Prefer 
// radiobuttons as a secondary effect) when indicated by parameter bInit
//
function doFilter( strEdition, bNewSelected, bInit )
{
  doStartUpdate();

  // Check there is a change
  
  var oVisibility = PreferencesMap.show;
  var bOldSelected = oVisibility[ strEdition ];
  var bHasChange = ( bOldSelected != bNewSelected );
  
  // Check preconditions
  if ( bHasChange && !bInit )
  {
    var bOk = true;
    
    var oNewVisibility = shallowCopy( oVisibility );
    oNewVisibility[ strEdition ] = bNewSelected;
    
    var n = PreferencesConstraintsArray.length;
    for ( i = 0; i < n; i++ )
    {
      var bOk = PreferencesConstraintsArray[i]( oNewVisibility, PreferencesMap );
      if ( !bOk )
      {
        // If any precondition is not met undoes click and avoiding any further actions
        var strCheckId = "#filter" + strEdition;
        var oCheckHTML = $( strCheckId );
        oCheckHTML.prop( "checked", !bNewSelected );
        
        doEndUpdate();
        return;
      }
    }
  }
  
  if ( !bHasChange && !bInit )
  {
    doEndUpdate();
    return;
  }

  // If there is a change, then update preference and continue
  oVisibility[ strEdition ] = bNewSelected;
  doNotifyUpdateEvent( EVENT_FILTER_CHANGE );
  
  // Enable/disable preference checkboxes
  var strPreferId = "#prefer" + strEdition;
  var oHTMLRadio = $( strPreferId );
  if ( oHTMLRadio.length )
  {    
    var strPrefer = PreferencesMap.prefer;

    var strLabelId = "#label_prefer" + strEdition;
    if ( bNewSelected )
    {
      // Ensure the radiobutton is enabled
      oHTMLRadio.attr( "disabled", false );
      $ ( strLabelId ).css('opacity', '1');
      
      
      if ( bInit )
      {
        // Resets radios during initialization
        if ( strPrefer == strEdition )
        {
          oHTMLRadio.prop( "checked", true );
          doNotifyUpdateEvent( EVENT_EDITION_CHANGE );
        }
      }
      else
      {
        if ( strPrefer == "" )
        {
          PreferencesMap.prefer = strEdition;
          doNotifyUpdateEvent( EVENT_EDITION_CHANGE );

          oHTMLRadio.prop( "checked", true );
          doPrefer( strEdition, true );
        }
      }
    }
    else
    {
      // Disables the radiobutton
      oHTMLRadio.attr( "disabled", true );
      oHTMLRadio.prop( "checked", false );
      $ ( strLabelId ).css('opacity', '.2');

      // If this option was the preferred, tries to move it to another option if available
      if ( strPrefer == strEdition )
      {
        doSearchNewPrefer( strEdition );
      }
    }
  }
  
  var n = ItemTree.length;
  for (var i = 0; i < n; i++ )
  {
    var o = ItemTree[ i ];
    var arrEdition = o.edition || [];
    
    // Compute current visibility from preferences (or used stored value)
    
    var bOldVisible = o.visible;
    
    // Compute new visibility from input 
    
    var bNewVisible = isVisible( arrEdition, oVisibility )
    
    // If they differ then... (two options)
    
    if ( bOldVisible != bNewVisible )
    {
      var strInputId = o.inputFieldId;
      var strVisibilitySpan = "#show_" + strInputId;
      var oHTMLSpan = $( strVisibilitySpan );

      // If new visibility is hidden then
      if (  !bNewVisible )
      {
      
        // 1. hide input        
        oHTMLSpan.hide();
        
        // 2. hide children group if open
        // 3. clear current values
        // 4. hide items associated to class    
        var oHTMLCheckbox = $( "#" + strInputId );
        if ( o.children )
        {
          var bOldSelected = oHTMLCheckbox.prop( "checked" );
          if ( bOldSelected )
          {
            //doCheckboxGroup( oHTMLCheckbox ); 
            oHTMLCheckbox.click();
          }
          
          if ( o.open )
          {
            var oHTMLLink = $( "a#" + strInputId );
            doToggle( oHTMLLink );
          }
        }
        else
        {
          var strClassName = o.className;
          var bOldSelected = doEval( strClassName );
          if ( bOldSelected )
          {
            oHTMLCheckbox.click();
          }
        }
      }
      else
      {
        // If new visibility is visible the show 
        oHTMLSpan.show();
      }

      // Update preferences
      o.visible = bNewVisible;
    }
  } 

  doEndUpdate();
}


function doWrapFilter( oHTMLCheck ) 
{
  var strValue = oHTMLCheck.val();
  var bSelected = oHTMLCheck.prop( "checked" );
  
  doFilter( strValue, bSelected );
}



function doWrapMarkAdaptations( oHTMLCheck )
{
  var strValue = oHTMLCheck.val();
  var bSelected = oHTMLCheck.prop( "checked" );
  
  doMarkAdaptations( strValue, bSelected );
}



//
// Initialize Filters Tab with Preferences
//
function doInitPreferences()
{
  doStartUpdate();

  var oVisibility = PreferencesMap.show;
  for ( var strEdition in oVisibility )
  {
    var bSelected = oVisibility[ strEdition ];
    doFilter( strEdition, bSelected, true );
    
    $( "#filter" + strEdition ).prop( "checked", bSelected );
  }

  var bMark = PreferencesMap.mark;
  doMarkAdaptations( null, bMark, true );

  $( "#markAdaptations" ).prop( "checked", bMark );

  doEndUpdate();


  $( ".inputFilter" ).click( 
    function() { doWrapFilter( $(this) ); } 
  );  

  $( ".inputPrefer" ).click( 
    function() { doWrapPrefer( $(this) ); } 
  );  

  $( ".inputMark" ).click( 
    function() { doWrapMarkAdaptations( $(this) ); } 
  );  

}


// Get Fixed edition from HTML
//
function getFixedEdition()
{
  var strFixedEdition = $( "#" + TABS_CONTAINER_ID ).data( "fixedEdition" );
  return strFixedEdition;
}

// Get fan expansions setting from HTML
//
function getFanExpansions()
{
  var strFanExpansions = $( "#" + TABS_CONTAINER_ID ).data( "fanExpansions" );
  return strFanExpansions;
}

// Get preselected expansions setting from HTML
//
function getPreselectedExpansions()
{
  var strPreselectedExpansions = $( "#" + TABS_CONTAINER_ID ).data( "preselectedExpansions" );
  return strPreselectedExpansions;
}


function setCheckAsReadOnly( strClassName, bComplex )
{
  var oExpComplex = /^show(.*)_Full$/;
  var oExpSimple = /^show(.*)$/;
  var oExp = ( bComplex ? oExpComplex : oExpSimple );
  var arrExp = oExp.exec( strClassName );
  var strSelector = arrExp[1];
  if ( strSelector )
  {
    var strInputId = "input" + strSelector;
    $( "#" + strInputId ).attr( "readonly", "readonly" );
  }
}


// Initializes th list of preselected classes
//   Precond: DerivedClasses from the Result Strings have to be initialized, as
//   we are using derived classes XXX_Full as a shortcut to avoid duplicating the
//   definition of all the classes contained in each expansion
//
function doInitializePreselectedClasses()
{

  var arrClassNames = PreferencesMap.preselectedExpansions || []; 
  var arrClassesRes = [];
  var nClassNames = arrClassNames.length;

  for ( var i = 0; i < nClassNames; i++ )
  {
    var strClassName = arrClassNames[ i ];
      
    var oClassName = ClassNameMap[ strClassName ];

    if( !oClassName )
    {
      console.log( "Undefined preselected class: " + strClassName );
      continue;
    }

    var arrExpression = oClassName.expression;
    if ( arrExpression )
    {
      setCheckAsReadOnly( strClassName, true ); // XXX_Full class name

      // It is an array
      var nExpressionLength = arrExpression.length;
        
      // Adds all class names skipping the first array element (an operator function)
      for ( var j = 1; j < nExpressionLength; j++ )
      {
        var strExpressionClassName = arrExpression[ j ];
        arrClassesRes.push( strExpressionClassName );
        setCheckAsReadOnly( strExpressionClassName, false );
      }

    }
    else
    {
      // It is a class name
        
      arrClassesRes.push( strClassName );
      setCheckAsReadOnly( strClassName, false );
    }          
  }

  PreferencesMap.preselectedExpansions = arrClassesRes;
}


// Loads the preselected expansions and disable their associated checkboxes 
//
function doInitPreselectedExpansions( bInit )
{
  doStartUpdate();

  var oVisibility = PreferencesMap.show;
  
  // Resolves XXXX_Full classes
  if ( bInit )
  {
    doInitializePreselectedClasses();
  }

  var arrExpansionPreset = PreferencesMap.preselectedExpansions || []; 
  var n = arrExpansionPreset.length;
  for ( var i = 0; i < n; i++ )
  {
    var strClassName = arrExpansionPreset[ i ];
    var o = ClassNameMap[ strClassName ]; 

    if ( !o )
    {
      console.log( "Undefined preselected class: " + strClassName );
      continue;
    }
    
    // Only simple class names are processed
    if ( !o.expression )
    {
      var arrEdition = o.edition || [];
      
      var bIsVisible = isVisible( arrEdition, oVisibility )
      if ( bIsVisible )
      {
        var oHTMLCheck = $( "#" + o.inputFieldId );
        var bOldSelected = o.selected;
        if ( bOldSelected == false )
        {
          oHTMLCheck.click();  
        }

        //oHTMLCheck.click( function () { return false; } );
      }
    }
  }

  doEndUpdate();
}


//
// Adjustment for Scoring pages
//
function updateHTMLScoringToTop() {
    var height = 0;
    var obj = $('#breadcrumbs');
    if (obj.length>0) {
        height+=obj.height()*1;
    }
    var obj = $('#summary_table');
    if (obj.length>0) {
        height+=obj.height()*1;
    }
    $('html').css('scroll-padding-top',height+'px');
}


// Runs all initialization for
// a. Expansion selector tabs
// b. Preference tab 
//
function doInit()
{
  /* --- Not used --
  // Check if the URL specifies a preferred edition: C1, C2, WE
  var strEdition = getUrlParam( "edition" );
  */

  // Reads Fixed Edition setting indicated included page and resets resets 
  // PreferenceMap data structure
  //
  var strEdition = getFixedEdition();
  if ( ( strEdition == "C1" ) || ( strEdition == "C2" ) || ( strEdition == "WE" )) 
  {
    var oVisibilityMap = PreferencesMap.show;
    for ( var strEditionKey in oVisibilityMap )
    {
      oVisibilityMap[ strEditionKey ] = ( strEditionKey == strEdition );
    }
    PreferencesMap.prefer = strEdition;
    PreferencesMap.fixedEdition = strEdition;
    PreferencesMap.marks = false;
  }

  // Reads Fan Expansions settings to indicate if fan expansions should be considered 
  //
  var strFanExpansions = getFanExpansions();
  if ( strFanExpansions != true )
  {
    var bShowfanExpansions = PreferencesMap.fanExpansions = ( strFanExpansions == "true" );

    if ( !bShowfanExpansions )
    {
      $( "#tabs-fan-label" ).hide();
      $( "#tabs-fan" ).hide();
      $( "#tabs-fan-result" ).hide();

      //var oTabs = $( "#" + TABS_ID ).tabs();
      //oTabs.tabs( "refresh" );
     }
  }

  // Reads Preselected Expansions to be marked permanently in the selector
  //
  var strPreselectedExpansions = getPreselectedExpansions();
  if ( strPreselectedExpansions )
  {
    var arrPreselectedExpansions = strPreselectedExpansions.split( "," );
    PreferencesMap.preselectedExpansions = arrPreselectedExpansions;
  }

  // Initialize footnotes
  doInitFootnotes();

  // Initialize language mmapings
  doInitLanguageMap()

  // Initialize tabs and visibility
  doInitTabs();

  // Initialize preselected expansions
  doInitPreselectedExpansions( true );

  // Adjust top scrolling for Scoring pages
  updateHTMLScoringToTop();

  UpdateStatus.initializing = false;
}

// Callback to do the all initialization
//
function doWrapInit()
{
  if ( $( "#" + TABS_CONTAINER_ID ).hasClass( SHOW_WHEN_DONE_CLASS ) )
  {
    $( "#" + TABS_CONTAINER_ID ).html( strPage );
    $( "#" + TABS_ID ).tabs( {
        collapsible: true
    } );

    doInit();

    $( "." + HIDE_WHEN_DONE_CLASS ).hide();
    $( "." + SHOW_WHEN_DONE_CLASS ).show();

    // Needed after the Result lists are visible, otherwise separators do not show
    doUpdateResultListSeparators();

   }
}


// Watchdog function to check if initialization is possible
//
function isActionPossible()
{
  return ( !!( window.strPage ) && !!( $( "#" + TABS_ID ).tabs ) );
}

// Initialization action to be triggered when ready
//
function doAction()
{
  doWrapInit();
}

// Wrapper for async loading
//
function doActionWrapper()
{
  console.log( "Async Start Iteration: #" + ( iCounter++ ) );
  if ( isActionPossible() )
  {
    clearInterval( iVal );
    doAction()
  }
}

var iVal = null;
var iCounter = 0;

// Kicks-off initialization. If sync loading is not ready, triggers async approach 
//
function doStart()
{
  if( isActionPossible() )
  {
    console.log( "Sync Start Done!" );
    doAction();
  }
  else
  {
    console.log( "Async Start Iteration: #" + ( iCounter++ ) );
    iVal = setInterval( doActionWrapper, 500);
  }
} 


$( document ).ready( doStart );