Difference between revisions of "MediaWiki:JKey.js"

From FloraWiki - das Wiki zur Schweizer Flora
Jump to: navigation, search
m (test expandToggleAllExtras();)
m (ESlint fix: single quotes for string)
Line 18: Line 18:
  
 
// set ECMAScript 5 Strict Mode. Done globally, not within each function. Later the whole jKey should be wrapped as a gadget providing scope for this
 
// set ECMAScript 5 Strict Mode. Done globally, not within each function. Later the whole jKey should be wrapped as a gadget providing scope for this
"use strict";
+
'use strict';
  
 
////////////////////////
 
////////////////////////
Line 39: Line 39:
 
  */
 
  */
 
jException.prototype.toString = function () {
 
jException.prototype.toString = function () {
   var message = "JKey Exception " + this.errorCode + "\n\n",
+
   var message = 'JKey Exception ' + this.errorCode + '\n\n',
 
   key;
 
   key;
 
   for (key in this.variables) {
 
   for (key in this.variables) {
 
     // only own properties, not inherited ones:
 
     // only own properties, not inherited ones:
 
     if (this.variables.hasOwnProperty(key)) {
 
     if (this.variables.hasOwnProperty(key)) {
       message += " " + key + ": " + this.variables[key] + "\n";
+
       message += ' ' + key + ': ' + this.variables[key] + '\n';
 
     }
 
     }
 
   }
 
   }
Line 51: Line 51:
 
// also override toString of default Error constructor
 
// also override toString of default Error constructor
 
Error.prototype.toString = function () {
 
Error.prototype.toString = function () {
   return "Javascript exception: " + this.name  
+
   return 'Javascript exception: ' + this.name  
     + "\n\nMessage: " + this.message  
+
     + '\n\nMessage: ' + this.message  
     + "\nFileName: " + this.fileName  
+
     + '\nFileName: ' + this.fileName  
     + "\nLineNumber: " + this.lineNumber;
+
     + '\nLineNumber: ' + this.lineNumber;
 
};
 
};
 
/*
 
/*
Line 63: Line 63:
 
   if (window.console) { // IE dev.tools (=F12), or FF firebug console ENABLED
 
   if (window.console) { // IE dev.tools (=F12), or FF firebug console ENABLED
 
     console.log(exception); // TODO: in ie watch-console there is no debug fct?!
 
     console.log(exception); // TODO: in ie watch-console there is no debug fct?!
   } else { // perhaps in FF write to browser-javascript-console: throw new Error("text");
+
   } else { // perhaps in FF write to browser-javascript-console: throw new Error('text');
 
     alert(exception);
 
     alert(exception);
 
   }
 
   }
Line 90: Line 90:
 
     // create base header
 
     // create base header
 
     this.item = $('<tr class="histHeader"/>')
 
     this.item = $('<tr class="histHeader"/>')
       .append($('<td class="histHeaderContent" colspan="3"/>').html((newContent ? newContent : $.resource("jKey_historyHeading")) + " &nbsp; ")
+
       .append($('<td class="histHeaderContent" colspan="3"/>').html((newContent ? newContent : $.resource('jKey_historyHeading')) + ' &nbsp; ')
 
         .append($('<span class="histHeaderActive"/>')
 
         .append($('<span class="histHeaderActive"/>')
           .append($.imglinkBuilder("jKey_historyActive", "", "class='histActiveOn' onclick='return jkeySwitchHistory(this);'"))
+
           .append($.imglinkBuilder('jKey_historyActive', '', 'class="histActiveOn" onclick="return jkeySwitchHistory(this);"'))
 
           .toggle(!isFirstHistory)
 
           .toggle(!isFirstHistory)
 
           ));
 
           ));
Line 104: Line 104:
 
   */
 
   */
 
   this.isActive = function () {
 
   this.isActive = function () {
     return this.item.find("span.histHeaderActive a").hasClass("histActiveOn");
+
     return this.item.find('span.histHeaderActive a').hasClass('histActiveOn');
 
   };
 
   };
 
   /*
 
   /*
Line 111: Line 111:
 
   */
 
   */
 
   this.setCurrBlockActive = function (isActiveHistory) { // do not use imglinkBuilder, replaceWith kills layout
 
   this.setCurrBlockActive = function (isActiveHistory) { // do not use imglinkBuilder, replaceWith kills layout
     var histHeaderActiveCell = this.item.find("span.histHeaderActive a");
+
     var histHeaderActiveCell = this.item.find('span.histHeaderActive a');
 
     // change class & image
 
     // change class & image
     histHeaderActiveCell.attr({"class": (isActiveHistory ? "histActiveOn" : "histActiveOff")});
+
     histHeaderActiveCell.attr({'class': (isActiveHistory ? 'histActiveOn' : 'histActiveOff')});
     histHeaderActiveCell.find("img")
+
     histHeaderActiveCell.find('img')
     .attr("src", $.resource(isActiveHistory ? "jKey_historyActive" : "jKey_historyInactive"))
+
     .attr('src', $.resource(isActiveHistory ? 'jKey_historyActive' : 'jKey_historyInactive'))
     .attr("title", $.resource(isActiveHistory ? "jKey_historyActiveTooltip" : "jKey_historyInactiveTooltip"));
+
     .attr('title', $.resource(isActiveHistory ? 'jKey_historyActiveTooltip' : 'jKey_historyInactiveTooltip'));
 
   };
 
   };
 
}// HistoryHeader()
 
}// HistoryHeader()
Line 125: Line 125:
 
   /* Description: create new subheading with newContent string */
 
   /* Description: create new subheading with newContent string */
 
   this.create = function (newContent) {
 
   this.create = function (newContent) {
     this.item = $("<tr class='histConfirmSubhdg'/>")
+
     this.item = $('<tr class="histConfirmSubhdg"/>')
       .append($("<td colspan='3' class='histConfirmSubhdgContent'/>").html(newContent));
+
       .append($('<td colspan="3" class="histConfirmSubhdgContent"/>').html(newContent));
 
     return this.item;
 
     return this.item;
 
   };
 
   };
 
}
 
}
/*
+
/**
 
  * Description: history result CONSTRUCTOR
 
  * Description: history result CONSTRUCTOR
 +
*
 +
* @returns {HistoryResult}
 
  */
 
  */
 
function HistoryResult() {
 
function HistoryResult() {
 
   /* Description: create new result item with newContent string */
 
   /* Description: create new result item with newContent string */
 
   this.create = function (newContent) {
 
   this.create = function (newContent) {
     this.item = $("<tr class='histResult'/>")
+
     this.item = $('<tr class="histResult"/>')
       .append($("<td class='histResultSymbol'/>").text(""))
+
       .append($('<td class="histResultSymbol"/>').text(''))
       .append($("<td colspan='2' class='histResultContent'/>").html(newContent));
+
       .append($('<td colspan="2" class="histResultContent"/>').html(newContent));
 
     return this.item;
 
     return this.item;
 
   };
 
   };
Line 145: Line 147:
 
/*
 
/*
 
  * Description: history nested CONSTRUCTOR
 
  * Description: history nested CONSTRUCTOR
 +
* @returns {HistoryNested}
 
  */
 
  */
 
function HistoryNested() {
 
function HistoryNested() {
Line 158: Line 161:
 
   };
 
   };
 
}
 
}
/*
+
/**
 
  * Description: history step CONSTRUCTOR
 
  * Description: history step CONSTRUCTOR
 +
* @returns {HistoryStep}
 
  */
 
  */
 
function HistoryStep() {
 
function HistoryStep() {
Line 173: Line 177:
 
     // create base step
 
     // create base step
 
     this.item = $('<tr class ="histStep"/>')
 
     this.item = $('<tr class ="histStep"/>')
       .append($('<td class="histStepNumber"/>').text(newStepNumber + "."))
+
       .append($('<td class="histStepNumber"/>').text(newStepNumber + '.'))
 
       .append('<td class="histStepContent"/>')
 
       .append('<td class="histStepContent"/>')
 
     // Create revise-history action (changable to confirm).
 
     // Create revise-history action (changable to confirm).
Line 181: Line 185:
 
           .append(
 
           .append(
 
             $.linkBuilder(
 
             $.linkBuilder(
               "jKey_historyRevise",
+
               'jKey_historyRevise',
               "",
+
               '',
               "#" + confCoupletID,
+
               '#' + confCoupletID,
               " class='histStepActionRevise small-linkbtn' onclick='return jkeyHistoryAction(this, \"" + revCoupletID + "\", \"" + confCoupletID + "\");'"
+
               ' class="histStepActionRevise small-linkbtn" onclick="return jkeyHistoryAction(this, \'' + revCoupletID + '\', \'' + confCoupletID + '\');"'
 
             )
 
             )
 
           )
 
           )
Line 197: Line 201:
 
   */
 
   */
 
   this.setUncertainty = function (isUncertain) {
 
   this.setUncertainty = function (isUncertain) {
     this.item.find("td.histStepContent span.histStepCertainty")
+
     this.item.find('td.histStepContent span.histStepCertainty')
       .html(isUncertain ? ("&nbsp;" + $.resource("jKey_historyUncertainFlag") + "&nbsp;") : "");
+
       .html(isUncertain ? ('&nbsp;' + $.resource('jKey_historyUncertainFlag') + '&nbsp;') : '');
 
   };
 
   };
 
   /*
 
   /*
Line 204: Line 208:
 
   */
 
   */
 
   this.getUncertainty = function () {
 
   this.getUncertainty = function () {
     return this.item.find("td.histStepContent span.histStepCertainty").text().length > 0;
+
     return this.item.find('td.histStepContent span.histStepCertainty').text().length > 0;
 
   };
 
   };
 
   /*
 
   /*
Line 211: Line 215:
 
   */
 
   */
 
   this.setContent = function (newContent) {
 
   this.setContent = function (newContent) {
     this.item.find("td.histStepContent")
+
     this.item.find('td.histStepContent')
 
       .empty()
 
       .empty()
 
       .append(newContent)
 
       .append(newContent)
Line 220: Line 224:
 
   */
 
   */
 
   this.getStepNumber = function () {
 
   this.getStepNumber = function () {
     return parseInt(this.item.find("td.histStepNumber").text(), 10); // parseInt example: " 8." will return 8
+
     return parseInt(this.item.find('td.histStepNumber').text(), 10); // parseInt example: " 8." will return 8
 
   };
 
   };
 
   /*
 
   /*
Line 227: Line 231:
 
   */
 
   */
 
   this.setConfirmable = function (setToConfirm) {
 
   this.setConfirmable = function (setToConfirm) {
     this.item.find("td.histStepActions a")
+
     this.item.find('td.histStepActions a')
       .attr({"class": (setToConfirm ? "histStepActionConfirm small-linkbtn" : "histStepActionRevise small-linkbtn")})
+
       .attr({'class': (setToConfirm ? 'histStepActionConfirm small-linkbtn' : 'histStepActionRevise small-linkbtn')})
       .text($.resource((setToConfirm ? "jKey_historyConfirm" : "jKey_historyRevise")));
+
       .text($.resource((setToConfirm ? 'jKey_historyConfirm' : 'jKey_historyRevise')));
 
   };
 
   };
 
   /*
 
   /*
Line 235: Line 239:
 
   */
 
   */
 
   this.isConfirmable = function () {
 
   this.isConfirmable = function () {
     return this.item.find("td.histStepActions a").is(".histStepActionConfirm");
+
     return this.item.find('td.histStepActions a').is('.histStepActionConfirm');
 
   };
 
   };
 
}
 
}
Line 265: Line 269:
 
   */
 
   */
 
   this.createBlock = function (active, isFirstHistory, newContent) {
 
   this.createBlock = function (active, isFirstHistory, newContent) {
     return $('<table cellpadding="0" cellspacing="0" class="' + (isFirstHistory ? "histTable" : "histBlock") + '"/>')
+
     return $('<table cellpadding="0" cellspacing="0" class="' + (isFirstHistory ? 'histTable' : 'histBlock') + '"/>')
 
       .append('<tr class="histLayout"/><td/><td/><td/></tr>')
 
       .append('<tr class="histLayout"/><td/><td/><td/></tr>')
 
       .append(
 
       .append(
Line 271: Line 275:
 
           active,
 
           active,
 
           isFirstHistory,
 
           isFirstHistory,
           "<b>" + (newContent) ? newContent : $.resource("jKey_historyHeading") + "</b>"
+
           '<b>' + (newContent) ? newContent : $.resource('jKey_historyHeading') + '</b>'
 
         )
 
         )
 
       );
 
       );
Line 308: Line 312:
 
   this.getfirstConfirmableStep = function () {
 
   this.getfirstConfirmableStep = function () {
 
     var retValue = null,
 
     var retValue = null,
       jAllSteps = jCurrHistBlock.find("tr:first").nextAll("tr.histStep"),
+
       jAllSteps = jCurrHistBlock.find('tr:first').nextAll('tr.histStep'),
 
       parentThis;
 
       parentThis;
 
     if (jAllSteps.length) { // steps exist
 
     if (jAllSteps.length) { // steps exist
Line 329: Line 333:
 
     var itemFound = false,
 
     var itemFound = false,
 
       // get steps within block, not including nested steps
 
       // get steps within block, not including nested steps
       jAllSteps = jCurrHistBlock.find("tr:first").nextAll("tr.histStep"),
+
       jAllSteps = jCurrHistBlock.find('tr:first').nextAll('tr.histStep'),
 
       parentThis;
 
       parentThis;
 
     if (jAllSteps.length) { // entries exists
 
     if (jAllSteps.length) { // entries exists
Line 337: Line 341:
 
           itemFound = true; // true once item was passed in loop
 
           itemFound = true; // true once item was passed in loop
 
           // add confirm subheading in front of confirmable steps
 
           // add confirm subheading in front of confirmable steps
           $(this).before(parentThis.createHistoryConfirmSubheading("<i>" + $.resource("jKey_historyConfirmable") + "</i>"));
+
           $(this).before(parentThis.createHistoryConfirmSubheading('<i>' + $.resource('jKey_historyConfirmable') + '</i>'));
 
         }
 
         }
 
         // update history actions
 
         // update history actions
Line 349: Line 353:
 
   this.cleanupAfter = function (jStep) {
 
   this.cleanupAfter = function (jStep) {
 
     // rework history if decision path has changed. Remove confirm-sub-heading, item itself & following siblings
 
     // rework history if decision path has changed. Remove confirm-sub-heading, item itself & following siblings
     jStep.prevAll("tr.histConfirmSubhdg").remove();
+
     jStep.prevAll('tr.histConfirmSubhdg').remove();
     jStep.nextAll("tr").andSelf().remove();
+
     jStep.nextAll('tr').andSelf().remove();
 
   };
 
   };
 
   this.cleanupNestedBlocks = function () {
 
   this.cleanupNestedBlocks = function () {
 
     // find last normal history step (or history header; fallback if no steps exists, e.g. when using try-all as first decision)
 
     // find last normal history step (or history header; fallback if no steps exists, e.g. when using try-all as first decision)
     var jStep = jCurrHistBlock.find("tr:first").nextAll("tr.histHeader, tr.histStep").filter(":last");
+
     var jStep = jCurrHistBlock.find('tr:first').nextAll('tr.histHeader, tr.histStep').filter(':last');
 
     if (jStep.length) {
 
     if (jStep.length) {
       jStep.nextAll("tr").remove();
+
       jStep.nextAll('tr').remove();
 
     }
 
     }
 
   };
 
   };
Line 399: Line 403:
 
   */
 
   */
 
   this.isActive = function () {
 
   this.isActive = function () {
     historyHeader.item = jCurrHistBlock.find("tr.histHeader:first");
+
     historyHeader.item = jCurrHistBlock.find('tr.histHeader:first');
 
     return historyHeader.isActive();
 
     return historyHeader.isActive();
 
   };
 
   };
Line 407: Line 411:
 
   */
 
   */
 
   this.setCurrBlockActive = function (newIsActive) {
 
   this.setCurrBlockActive = function (newIsActive) {
     historyHeader.item = jCurrHistBlock.find("tr.histHeader:first");
+
     historyHeader.item = jCurrHistBlock.find('tr.histHeader:first');
 
     historyHeader.setCurrBlockActive(newIsActive);
 
     historyHeader.setCurrBlockActive(newIsActive);
 
   };
 
   };
Line 420: Line 424:
 
     }
 
     }
 
     var stepNumber = 0,
 
     var stepNumber = 0,
       jLastStep = historyBlock.find("tr:first").nextAll("tr.histStep:last");
+
       jLastStep = historyBlock.find('tr:first').nextAll('tr.histStep:last');
 
     if (jLastStep.length) {
 
     if (jLastStep.length) {
 
       stepNumber = this.withHistoryStep(jLastStep.get(0)).getStepNumber();
 
       stepNumber = this.withHistoryStep(jLastStep.get(0)).getStepNumber();
     } else if (!historyBlock.is(".histTable")) { // unless outermost and not step (return 0): recurse to parent
+
     } else if (!historyBlock.is('.histTable')) { // unless outermost and not step (return 0): recurse to parent
 
       historyBlock = this.getParentBlock();
 
       historyBlock = this.getParentBlock();
 
       return this.getNewStepNumber(historyBlock);
 
       return this.getNewStepNumber(historyBlock);
Line 433: Line 437:
 
   */
 
   */
 
   this.isFirstStepInBlockConfirmableStep = function () {
 
   this.isFirstStepInBlockConfirmableStep = function () {
     var firstStep = jCurrHistBlock.find("tr:first").nextAll("tr.histStep:first");
+
     var firstStep = jCurrHistBlock.find('tr:first').nextAll('tr.histStep:first');
     return (firstStep.prev("tr").hasClass("histConfirmSubhdg"));
+
     return (firstStep.prev('tr').hasClass('histConfirmSubhdg'));
 
   };
 
   };
 
   /*
 
   /*
Line 440: Line 444:
 
   */
 
   */
 
   this.firstNestedStep = function () {
 
   this.firstNestedStep = function () {
     return jCurrHistBlock.find("tr:first").nextAll("tr.histNested:first");
+
     return jCurrHistBlock.find('tr:first').nextAll('tr.histNested:first');
 
   };
 
   };
 
   /*
 
   /*
Line 446: Line 450:
 
   */
 
   */
 
   this.getParentBlock = function () {
 
   this.getParentBlock = function () {
     // first closest("table.histBlock, table.histTable") will find own block, then up and find parent:
+
     // first closest('table.histBlock, table.histTable') will find own block, then up and find parent:
     var jBlock = jCurrHistBlock.closest("table.histBlock, table.histTable");
+
     var jBlock = jCurrHistBlock.closest('table.histBlock, table.histTable');
     if (!jBlock.is(".histTable")) { // unless already outermost
+
     if (!jBlock.is('.histTable')) { // unless already outermost
       jBlock = jBlock.parent().closest("table.histBlock, table.histTable");
+
       jBlock = jBlock.parent().closest('table.histBlock, table.histTable');
 
     }
 
     }
 
     return jBlock;
 
     return jBlock;
Line 472: Line 476:
 
  */
 
  */
 
function jkeyisKeyRef(jRefTarget) {
 
function jkeyisKeyRef(jRefTarget) {
   return (jRefTarget.is("td.dt-nodeid") || jRefTarget.is("tr.dt-row") || jRefTarget.is("div.decisiontree"));
+
   return (jRefTarget.is('td.dt-nodeid') || jRefTarget.is('tr.dt-row') || jRefTarget.is('div.decisiontree'));
 
}
 
}
  
Line 484: Line 488:
 
function jkeyTransformCouplet(jPlayerDiv, jDecisionRow, jPlayerCouplet) {
 
function jkeyTransformCouplet(jPlayerDiv, jDecisionRow, jPlayerCouplet) {
 
   if (jDecisionRow.length === 0) {
 
   if (jDecisionRow.length === 0) {
     throw new jException("NotFound", {
+
     throw new jException('NotFound', {
       info: "Transform w/o valid jDecisionRow"
+
       info: 'Transform w/o valid jDecisionRow'
 
     });
 
     });
 
   }
 
   }
 
   // row id is like id="Lz_1_row"; we need the id of first td inside: id="Lz_1"
 
   // row id is like id="Lz_1_row"; we need the id of first td inside: id="Lz_1"
   var currCoupletID = jDecisionRow.children("td[id]:first").attr("id"),
+
   var currCoupletID = jDecisionRow.children('td[id]:first').attr('id'),
 
     jNextCouplet,
 
     jNextCouplet,
 
     eachLeadout = function () {// this = a leadout link
 
     eachLeadout = function () {// this = a leadout link
 
       var nextCoupletID = this.hash,
 
       var nextCoupletID = this.hash,
         isInternalLink = ((nextCoupletID.length > 0) && (this.href.search("/" + wgPageName + "#") !== -1)),
+
         isInternalLink = ((nextCoupletID.length > 0) && (this.href.search('/' + wgPageName + '#') !== -1)),
 
         jKeyTable,
 
         jKeyTable,
 
         jNodeID;
 
         jNodeID;
Line 502: Line 506:
 
         isInternalLink = jkeyisKeyRef(jNextCouplet);
 
         isInternalLink = jkeyisKeyRef(jNextCouplet);
 
         if (isInternalLink) {
 
         if (isInternalLink) {
           if (jNextCouplet.is("td.dt-nodeid")) { // already is the target node-id
+
           if (jNextCouplet.is('td.dt-nodeid')) { // already is the target node-id
             nextCoupletID = jNextCouplet.attr("id");
+
             nextCoupletID = jNextCouplet.attr('id');
 
           } else { // might be row or div; get key div (closest finds itself), then table, then dt-nodeid
 
           } else { // might be row or div; get key div (closest finds itself), then table, then dt-nodeid
 
             // jKeyTable is not loop-invariable; a wiki page may have multiple keys!
 
             // jKeyTable is not loop-invariable; a wiki page may have multiple keys!
             jKeyTable = jNextCouplet.closest("div.decisiontree").find("table.dt-body:last");
+
             jKeyTable = jNextCouplet.closest('div.decisiontree').find('table.dt-body:last');
             jNodeID = jKeyTable.find("td.dt-nodeid:first");
+
             jNodeID = jKeyTable.find('td.dt-nodeid:first');
 
             isInternalLink = (jNodeID.length > 0);
 
             isInternalLink = (jNodeID.length > 0);
 
             if (isInternalLink) {
 
             if (isInternalLink) {
               nextCoupletID = jNodeID.attr("id");
+
               nextCoupletID = jNodeID.attr('id');
 
             } // else no leads found in div
 
             } // else no leads found in div
 
           }
 
           }
Line 516: Line 520:
 
       } // END if isInternalLink
 
       } // END if isInternalLink
 
       // Following is NOT an else, isInternalLink may have been changed.
 
       // Following is NOT an else, isInternalLink may have been changed.
       nextCoupletID = (!isInternalLink) ? "" : nextCoupletID;
+
       nextCoupletID = (!isInternalLink) ? '' : nextCoupletID;
 
       // prepare resultlink (page or internal subkey) for player
 
       // prepare resultlink (page or internal subkey) for player
 
       $(this)
 
       $(this)
         .attr("target", (isInternalLink ? "_self" : "_blank"))
+
         .attr('target', (isInternalLink ? '_self' : '_blank'))
         .addClass("linkbtn").removeAttr("style")
+
         .addClass('linkbtn').removeAttr('style')
 
         .click(function () {return jkeyDecision(this, false); });
 
         .click(function () {return jkeyDecision(this, false); });
       // pass autostart marker on (note: this.hash = this.hash + "jkey-autostart" is ok in FF, but IE8 behaves strangely. Using full href in IE8 seems ok!)
+
       // pass autostart marker on (note: this.hash = this.hash + 'jkey-autostart' is ok in FF, but IE8 behaves strangely. Using full href in IE8 seems ok!)
       this.href = this.href + (this.hash.length ? "" : "#") + "jkey-autostart";
+
       this.href = this.href + (this.hash.length ? '' : '#') + 'jkey-autostart';
 
       // save in data
 
       // save in data
       $.data(this, "coupletID", {curr: currCoupletID, next: nextCoupletID});
+
       $.data(this, 'coupletID', {curr: currCoupletID, next: nextCoupletID});
 
     },
 
     },
 
     eachLeadon = function () { // this = a leadon link
 
     eachLeadon = function () { // this = a leadon link
 
       var jThis = $(this);
 
       var jThis = $(this);
       if (jThis.parent().hasClass("leadon")) { // for leadon (but not leadontext) overwrite display text
+
       if (jThis.parent().hasClass('leadon')) { // for leadon (but not leadontext) overwrite display text
         jThis.html($.resource("jKey_coupletContinue"));
+
         jThis.html($.resource('jKey_coupletContinue'));
 
       }
 
       }
       jThis.addClass("linkbtn").removeAttr("style");
+
       jThis.addClass('linkbtn').removeAttr('style');
 
       // General problem: changing link onclick attribute works in FF, but is ignored by IE (known bug)
 
       // General problem: changing link onclick attribute works in FF, but is ignored by IE (known bug)
 
       // One general solution is to build complete new link and delete previous one.
 
       // One general solution is to build complete new link and delete previous one.
       // Also working is use of jquery.click(), but watch referencing "this". Here "this" is correct because of each().
+
       // Also working is use of jquery.click(), but watch referencing 'this'. Here 'this' is correct because of each().
 
       jThis.click(function () {return jkeyDecision(this, false); });
 
       jThis.click(function () {return jkeyDecision(this, false); });
 
       // save in data
 
       // save in data
       $.data(this, "coupletID", {curr: currCoupletID, next: this.hash.substring(1, this.hash.length)});
+
       $.data(this, 'coupletID', {curr: currCoupletID, next: this.hash.substring(1, this.hash.length)});
 
     },
 
     },
     //prefixID = function () {return "jK" + this.id; },
+
     //prefixID = function () {return 'jK' + this.id; },
 
     suffixID = function () {
 
     suffixID = function () {
 
       // leave the mw-customcollapsible untouched
 
       // leave the mw-customcollapsible untouched
       // if (this.id.indexOf("mw-customcollapsible") === 0) {
+
       // if (this.id.indexOf('mw-customcollapsible') === 0) {
 
       //  return this.id;
 
       //  return this.id;
 
       // } else {
 
       // } else {
       //  return this.id + "jK";
+
       //  return this.id + 'jK';
 
       // }
 
       // }
       return this.id + "jK";
+
       return this.id + 'jK';
 
     };
 
     };
 
   // Process all leads in couplet
 
   // Process all leads in couplet
Line 556: Line 560:
 
   // Notes: * Using nextAll().andSelf() would add first lead (jDecisionRow) at the end
 
   // Notes: * Using nextAll().andSelf() would add first lead (jDecisionRow) at the end
 
   // * Using .prev().nextAll() fails for first couplet of horizontal style, which has no row before!
 
   // * Using .prev().nextAll() fails for first couplet of horizontal style, which has no row before!
   jDecisionRow.parent().children("tr.dt-row[id^=" + currCoupletID.replace(/(:|\.)/g,'\\$1') + "_]").each(function () {
+
   jDecisionRow.parent().children('tr.dt-row[id^=' + currCoupletID.replace(/(:|\.)/g,'\\$1') + '_]').each(function () {
 
     var jClonedRow = $(this).clone(true, true),
 
     var jClonedRow = $(this).clone(true, true),
 
       jNodeCell;
 
       jNodeCell;
 
     // prefix id of decision row itself and all descendants
 
     // prefix id of decision row itself and all descendants
     //jClonedRow.find("[id]").andSelf().attr("id", prefixID);
+
     //jClonedRow.find('[id]').andSelf().attr('id', prefixID);
 
     //check if andSelf neccessary
 
     //check if andSelf neccessary
     //jClonedRow.find("[id]").andSelf().attr("id", suffixID);
+
     //jClonedRow.find('[id]').andSelf().attr('id', suffixID);
     jClonedRow.find("[id]").attr("id", suffixID);
+
     jClonedRow.find('[id]').attr('id', suffixID);
 
     /**
 
     /**
 
     * test try default jQuery.makeCollapsible Tue Aug 08 2017 14:25:15 GMT+0200 (CEST)
 
     * test try default jQuery.makeCollapsible Tue Aug 08 2017 14:25:15 GMT+0200 (CEST)
Line 571: Line 575:
 
     * .attr('class', function (index, attr) {
 
     * .attr('class', function (index, attr) {
 
     * // check for MediaWiki class mw-customtoggle-myKey and add jK suffix
 
     * // check for MediaWiki class mw-customtoggle-myKey and add jK suffix
     *  return attr.replace(/(mw-customtoggle-[^ ]+)/, "$1jK");
+
     *  return attr.replace(/(mw-customtoggle-[^ ]+)/, '$1jK');
 
     * });
 
     * });
 
     * // remove all MediaWiki's collapsible class added previously by $.fn.makeCollapsible()
 
     * // remove all MediaWiki's collapsible class added previously by $.fn.makeCollapsible()
Line 580: Line 584:
 
     */
 
     */
 
     // replace lead identifier with arrow (normal) or blank (horizontal side-by-side leads)
 
     // replace lead identifier with arrow (normal) or blank (horizontal side-by-side leads)
     jNodeCell = jClonedRow.find("td.dt-nodeid:first");
+
     jNodeCell = jClonedRow.find('td.dt-nodeid:first');
 
     // CHECK why coupletID must be added here in addition to data stored generally for each link
 
     // CHECK why coupletID must be added here in addition to data stored generally for each link
 
     // couplet ID has to be extracted and stored as data
 
     // couplet ID has to be extracted and stored as data
     $.data(jNodeCell.get(0), "couplet", {id : $.trim(jNodeCell.text())}); // needed for editor & multipleStep startup
+
     $.data(jNodeCell.get(0), 'couplet', {id : $.trim(jNodeCell.text())}); // needed for editor & multipleStep startup
     jNodeCell.html((jClonedRow.get(0).className.search(/dt-row-hor\w+/) === -1) ? "" : " "); //\u25ba is ►
+
     jNodeCell.html((jClonedRow.get(0).className.search(/dt-row-hor\w+/) === -1) ? '' : ' '); //\u25ba is ►
     jClonedRow.find("td.leadalt").empty();
+
     jClonedRow.find('td.leadalt').empty();
 
     // Transform all relevant leadout (= result pages) and leadon/leadontext (next couplet) links
 
     // Transform all relevant leadout (= result pages) and leadon/leadontext (next couplet) links
 
     // (depending on row mode, multiple links may exist)
 
     // (depending on row mode, multiple links may exist)
     jClonedRow.find("span.leadout a").each(eachLeadout);
+
     jClonedRow.find('span.leadout a').each(eachLeadout);
     jClonedRow.find("span.leadon a, div.leadontext a, span.leadontext a").each(eachLeadon);
+
     jClonedRow.find('span.leadon a, div.leadontext a, span.leadontext a').each(eachLeadon);
 
     // initialize $.fn.makeCollapsible() again
 
     // initialize $.fn.makeCollapsible() again
 
     jPlayerCouplet.append(jClonedRow.show());
 
     jPlayerCouplet.append(jClonedRow.show());
Line 612: Line 616:
 
  */
 
  */
 
function jkeyLoadCouplet(jPlayerDiv, coupletID, isUncertain) {
 
function jkeyLoadCouplet(jPlayerDiv, coupletID, isUncertain) {
   var jPlayerCouplet = jPlayerDiv.find("table.dt-body");
+
   var jPlayerCouplet = jPlayerDiv.find('table.dt-body');
 
   jPlayerCouplet.empty(); // flush
 
   jPlayerCouplet.empty(); // flush
 
   // coupletID could be 'a.34', jquery needs 'a\.34'
 
   // coupletID could be 'a.34', jquery needs 'a\.34'
   // $("#"... -> document scope, coupletID may be in other key on same page
+
   // $('#'... -> document scope, coupletID may be in other key on same page
   jkeyTransformCouplet(jPlayerDiv, $("#" + coupletID.replace(/\./g, "\\.")).closest("tr"), jPlayerCouplet);
+
   jkeyTransformCouplet(jPlayerDiv, $('#' + coupletID.replace(/\./g, '\\.')).closest('tr'), jPlayerCouplet);
 
   // after history actions: results div may have to be hidden, and certainty div re-displayed
 
   // after history actions: results div may have to be hidden, and certainty div re-displayed
   jPlayerDiv.find("div.jkeyResultMsg").hide();
+
   jPlayerDiv.find('div.jkeyResultMsg').hide();
   jPlayerDiv.find("div.certaintyDiv").show()
+
   jPlayerDiv.find('div.certaintyDiv').show()
     .find("input#decisionUncertain").get(0).checked = isUncertain; // setting checkbox
+
     .find('input#decisionUncertain').get(0).checked = isUncertain; // setting checkbox
 
}
 
}
  
Line 626: Line 630:
 
  * Description: toggle visibility of player controls (top right player area)
 
  * Description: toggle visibility of player controls (top right player area)
 
  * mode: string for current control mode;
 
  * mode: string for current control mode;
  *  values are "resumed", "overview", "newstart", "finished"
+
  *  values are 'resumed', 'overview', 'newstart', 'finished'
  * elementInKeyDiv: any element inside div.decisiontree or div.decisiontree itself, Either as DOM ref OR as "#id" string
+
  * elementInKeyDiv: any element inside div.decisiontree or div.decisiontree itself, Either as DOM ref OR as '#id' string
 
  * (.closest() works inclusive!), usually a caller of a control (link, button).
 
  * (.closest() works inclusive!), usually a caller of a control (link, button).
 
  * returns: jKeyDiv to be further used elsewhere
 
  * returns: jKeyDiv to be further used elsewhere
Line 636: Line 640:
 
     // when called based on undefined id from location.hash
 
     // when called based on undefined id from location.hash
 
     if (elementInKeyDiv === undefined) {return; }
 
     if (elementInKeyDiv === undefined) {return; }
     var jKeyDiv = $(elementInKeyDiv).closest("div.decisiontree"),
+
     var jKeyDiv = $(elementInKeyDiv).closest('div.decisiontree'),
       jKeyTable = jKeyDiv.find("table.dt-body:last"),
+
       jKeyTable = jKeyDiv.find('table.dt-body:last'),
       jPlayerDiv = jKeyDiv.find("div.jkeyPlayer"),
+
       jPlayerDiv = jKeyDiv.find('div.jkeyPlayer'),
       jPlayerCouplet = jPlayerDiv.find("table.dt-body"),
+
       jPlayerCouplet = jPlayerDiv.find('table.dt-body'),
       jResultDiv = jPlayerDiv.find("div.jkeyResultMsg"),
+
       jResultDiv = jPlayerDiv.find('div.jkeyResultMsg'),
       jControls = jKeyDiv.find(".jkeyControls"),
+
       jControls = jKeyDiv.find('.jkeyControls'),
 
       finished,
 
       finished,
 
       overview,
 
       overview,
 
       uniquePlayerID;
 
       uniquePlayerID;
     if (mode === "firststart") { // change permanently: first-start to restart icon/text, add overview/resume controls, removes  toggleAllExtras!
+
     if (mode === 'firststart') { // change permanently: first-start to restart icon/text, add overview/resume controls, removes  toggleAllExtras!
       jControls.find(".jkeyPlayerStartNew").show();
+
       jControls.find('.jkeyPlayerStartNew').show();
       jControls.find(".jkeyPlayerStart1st, .jkeyToggleAllExtras").hide();
+
       jControls.find('.jkeyPlayerStart1st, .jkeyToggleAllExtras').hide();
 
       window.scrollTo(0, jKeyDiv[0].offsetTop); // scroll to current key
 
       window.scrollTo(0, jKeyDiv[0].offsetTop); // scroll to current key
       mode = "newstart";
+
       mode = 'newstart';
 
     }
 
     }
     finished = (mode === "finished");
+
     finished = (mode === 'finished');
     overview = !(mode === "resumed" || mode === "newstart" || finished);
+
     overview = !(mode === 'resumed' || mode === 'newstart' || finished);
     jControls.find(".jkeyPlayerOverview").toggle(!overview);
+
     jControls.find('.jkeyPlayerOverview').toggle(!overview);
     jControls.find(".jkeyPlayerResume").toggle(overview);
+
     jControls.find('.jkeyPlayerResume').toggle(overview);
     jKeyDiv.find(".dt-header,.dt-header-toggle").toggle(overview); // metadata box (description, audience, etc.) and right-floating "more" to show metadata box
+
     jKeyDiv.find('.dt-header,.dt-header-toggle').toggle(overview); // metadata box (description, audience, etc.) and right-floating "more" to show metadata box
 
     // Show finished message only in finished mode (+ set below for resumed)
 
     // Show finished message only in finished mode (+ set below for resumed)
 
     jResultDiv.toggle(finished);
 
     jResultDiv.toggle(finished);
Line 661: Line 665:
 
     // all separately colored text (links etc.) must be handled separately (add all, then remove inside player)
 
     // all separately colored text (links etc.) must be handled separately (add all, then remove inside player)
 
     // NOTE: css opacity not an option, opacity of children can not increase (= made more visible) !
 
     // NOTE: css opacity not an option, opacity of children can not increase (= made more visible) !
     $("#bodyContent")
+
     $('#bodyContent')
     .addClass("jkdimmed")
+
     .addClass('jkdimmed')
     .find("a,a.new,a:visited,a:hover,.pseudolink,.linbtn,h2,h3,h4,h5,h6,.commonnames").addClass("jkdimmed");
+
     .find('a,a.new,a:visited,a:hover,.pseudolink,.linbtn,h2,h3,h4,h5,h6,.commonnames').addClass('jkdimmed');
     jKeyDiv.find(".jkdimmed").removeClass("jkdimmed");
+
     jKeyDiv.find('.jkdimmed').removeClass('jkdimmed');
 
     switch (mode) {
 
     switch (mode) {
     case ("overview"): // STOP player, hide player, show original overview player, undo low opacity content area
+
     case ('overview'): // STOP player, hide player, show original overview player, undo low opacity content area
       jKeyDiv.removeClass("jkeyCanvas");
+
       jKeyDiv.removeClass('jkeyCanvas');
 
       jKeyTable.show();
 
       jKeyTable.show();
 
       jPlayerDiv.hide();
 
       jPlayerDiv.hide();
       $("#bodyContent")
+
       $('#bodyContent')
       .removeClass("jkdimmed")
+
       .removeClass('jkdimmed')
       .find(".jkdimmed").removeClass("jkdimmed");
+
       .find('.jkdimmed').removeClass('jkdimmed');
 
       break;
 
       break;
     case ("newstart"):
+
     case ('newstart'):
 
       // Delete player-div in case of restart (also invalidates jPlayerCouplet)
 
       // Delete player-div in case of restart (also invalidates jPlayerCouplet)
 
       jPlayerDiv.remove();
 
       jPlayerDiv.remove();
Line 689: Line 693:
 
             + '<input type="checkbox" id="decisionUncertain" value="1" /> '
 
             + '<input type="checkbox" id="decisionUncertain" value="1" /> '
 
             + '<label for="decisionUncertain" style="font-weight:bold" title="'+$.resource("jKey_certaintyTooltip")+'">'
 
             + '<label for="decisionUncertain" style="font-weight:bold" title="'+$.resource("jKey_certaintyTooltip")+'">'
             + $.resource("jKey_certaintyLabel") + "</label>&nbsp;"
+
             + $.resource('jKey_certaintyLabel') + '</label>&nbsp;'
 
             + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '
 
             + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '
             + $.linkBuilder("jKey_tryAllAlternatives","","#"," class='small-linkbtn' onclick='return jkeyTryAll(this);' title='"+$.resource("jKey_tryAllAlternativesTooltip")+"'")
+
             + $.linkBuilder('jKey_tryAllAlternatives','','#',' class="small-linkbtn" onclick="return jkeyTryAll(this);" title="'+$.resource('jKey_tryAllAlternativesTooltip')+"'")
 
             + '</div>'
 
             + '</div>'
 
         );
 
         );
Line 697: Line 701:
 
       // generate unique playerID (multiple keys may exist on 1 page!):
 
       // generate unique playerID (multiple keys may exist on 1 page!):
 
       do {
 
       do {
         uniquePlayerID = "jkp_" + $.random(1, 9999999);
+
         uniquePlayerID = 'jkp_' + $.random(1, 9999999);
 
       } while (jkeyHistory.uniquePlayerID); // until ID not yet used
 
       } while (jkeyHistory.uniquePlayerID); // until ID not yet used
 
       // add id to connect with history
 
       // add id to connect with history
       jPlayerDiv.attr("id", uniquePlayerID);
+
       jPlayerDiv.attr('id', uniquePlayerID);
 
       // initalize player history class (lazy loading)
 
       // initalize player history class (lazy loading)
 
       jkeyHistory[uniquePlayerID] = new PlayerHistory(jPlayerDiv);
 
       jkeyHistory[uniquePlayerID] = new PlayerHistory(jPlayerDiv);
 
       // Initialize player with first decision row (table may start with spacer row)
 
       // Initialize player with first decision row (table may start with spacer row)
 
       jKeyTable.before(jPlayerDiv); // add to DOM
 
       jKeyTable.before(jPlayerDiv); // add to DOM
       jkeyTransformCouplet(jKeyDiv, jKeyTable.find("tr.dt-row:first"), jPlayerCouplet);
+
       jkeyTransformCouplet(jKeyDiv, jKeyTable.find('tr.dt-row:first'), jPlayerCouplet);
       // NOTE: NO BREAK here, fallthrough to "resumed" intended!
+
       // NOTE: NO BREAK here, fallthrough to 'resumed' intended!
     case ("resumed"): // = player resumed. This may have to show couplet or finished mode
+
     case ('resumed'): // = player resumed. This may have to show couplet or finished mode
 
       // style change for key div + hide original table & show table in player
 
       // style change for key div + hide original table & show table in player
       jKeyDiv.addClass("jkeyCanvas");
+
       jKeyDiv.addClass('jkeyCanvas');
 
       jKeyTable.hide();
 
       jKeyTable.hide();
 
       jPlayerCouplet.show();
 
       jPlayerCouplet.show();
Line 716: Line 720:
 
       jResultDiv.toggle(jResultDiv.text().length > 0);
 
       jResultDiv.toggle(jResultDiv.text().length > 0);
 
       break;
 
       break;
     case ("finished"): // empty current couplet, hide certainty checkbox
+
     case ('finished'): // empty current couplet, hide certainty checkbox
 
       jPlayerCouplet.empty();
 
       jPlayerCouplet.empty();
       jPlayerDiv.find("div.certaintyDiv").hide();
+
       jPlayerDiv.find('div.certaintyDiv').hide();
 
       break;
 
       break;
 
     } // end case
 
     } // end case
Line 733: Line 737:
 
  */
 
  */
 
function jkeyLoadResult(jPlayerDiv, jResult) {
 
function jkeyLoadResult(jPlayerDiv, jResult) {
   jPlayerDiv.find("div.jkeyResultMsg").empty().html($.resource("jKey_mainResultMsg")).append(jResult.clone());
+
   jPlayerDiv.find('div.jkeyResultMsg').empty().html($.resource('jKey_mainResultMsg')).append(jResult.clone());
   jkeySetMode("finished", jPlayerDiv);
+
   jkeySetMode('finished', jPlayerDiv);
 
}
 
}
  
 
/*
 
/*
  * Description: perform "confirm" or "revise" action from history
+
  * Description: perform 'confirm' or 'revise' action from history
 
  * caller: DOM link; confirm if class=histStepActionConfirm, revise if histStepActionRevise
 
  * caller: DOM link; confirm if class=histStepActionConfirm, revise if histStepActionRevise
 
  * revCoupletID, confCoupletID: id of couplet to be revised or confirmed
 
  * revCoupletID, confCoupletID: id of couplet to be revised or confirmed
Line 746: Line 750:
 
     // get currently active history (find first & look in hierarchy)
 
     // get currently active history (find first & look in hierarchy)
 
     var jCaller = $(caller),
 
     var jCaller = $(caller),
       jStepItem = jCaller.closest("tr"), // history row around action link
+
       jStepItem = jCaller.closest('tr'), // history row around action link
       jPlayerDiv = jStepItem.closest("div.jkeyPlayer"),
+
       jPlayerDiv = jStepItem.closest('div.jkeyPlayer'),
       jCurrHistory = jkeyHistory[jPlayerDiv.attr("id")],
+
       jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
 
       firstConfirmableStep,
 
       firstConfirmableStep,
 
       nextDecisionIsUncertain;
 
       nextDecisionIsUncertain;
 
     // set history block to the one containing the caller (or outermost histTable itself)
 
     // set history block to the one containing the caller (or outermost histTable itself)
     jCurrHistory.changeActiveBlock(jCaller.closest("table.histBlock, table.histTable"));
+
     jCurrHistory.changeActiveBlock(jCaller.closest('table.histBlock, table.histTable'));
 
     // Handle possible history change
 
     // Handle possible history change
     if (jCaller.hasClass("histStepActionConfirm")) { // confirm was clicked
+
     if (jCaller.hasClass('histStepActionConfirm')) { // confirm was clicked
 
       revCoupletID = confCoupletID; // revCoupletID = next couplet to load
 
       revCoupletID = confCoupletID; // revCoupletID = next couplet to load
 
       // Advance to next history step
 
       // Advance to next history step
       jStepItem = jStepItem.nextAll("tr.histStep:first");
+
       jStepItem = jStepItem.nextAll('tr.histStep:first');
 
       // Update history step certainty from player checkbox
 
       // Update history step certainty from player checkbox
 
       firstConfirmableStep = jCurrHistory.getfirstConfirmableStep();
 
       firstConfirmableStep = jCurrHistory.getfirstConfirmableStep();
Line 763: Line 767:
 
         // firstConfirmableStep is the couplet shown in player!
 
         // firstConfirmableStep is the couplet shown in player!
 
         jCurrHistory.withHistoryStep(firstConfirmableStep)
 
         jCurrHistory.withHistoryStep(firstConfirmableStep)
           .setUncertainty(jPlayerDiv.find("div.certaintyDiv input#decisionUncertain").is(":checked"));
+
           .setUncertainty(jPlayerDiv.find('div.certaintyDiv input#decisionUncertain').is(':checked'));
 
       }
 
       }
 
     }
 
     }
     // Remove confirm "confirmable-decisions" sub-heading; will be recreated in updateConfirmability if necessary
+
     // Remove confirm 'confirmable-decisions' sub-heading; will be recreated in updateConfirmability if necessary
     jCurrHistory.getCurrBlock().find("tr.histConfirmSubhdg").remove();
+
     jCurrHistory.getCurrBlock().find('tr.histConfirmSubhdg').remove();
     if (revCoupletID === "") { // RESULT
+
     if (revCoupletID === '') { // RESULT
 
       // try-all may result in multiple alternative results. Thus confirming a result needs to refresh rather than re-display
 
       // try-all may result in multiple alternative results. Thus confirming a result needs to refresh rather than re-display
       jkeyLoadResult(jPlayerDiv, jCaller.closest("tr.histStep").next("tr.histResult").find("span.leadout"));
+
       jkeyLoadResult(jPlayerDiv, jCaller.closest('tr.histStep').next('tr.histResult').find('span.leadout'));
 
     } else { // Refresh player with couplet corresponding to jStep
 
     } else { // Refresh player with couplet corresponding to jStep
 
       nextDecisionIsUncertain = false; // default
 
       nextDecisionIsUncertain = false; // default
Line 794: Line 798:
 
  */
 
  */
 
function jkeySimplifiedLeadtxt(leadLinkCaller) {
 
function jkeySimplifiedLeadtxt(leadLinkCaller) {
   var jContainer = $(leadLinkCaller).closest("td.leadresult, th.leadtxt, td.leadtxt"), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
+
   var jContainer = $(leadLinkCaller).closest('td.leadresult, th.leadtxt, td.leadtxt'), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
 
     jSpan,
 
     jSpan,
 
     jSimplifiedLeadTxt;
 
     jSimplifiedLeadTxt;
   if (jContainer.hasClass("leadtxt")) {
+
   if (jContainer.hasClass('leadtxt')) {
 
     // occurs if leadspan itself is formatted as link (having both leadspan and leadontext class)
 
     // occurs if leadspan itself is formatted as link (having both leadspan and leadontext class)
     jSpan = jContainer.find("span.leadspan");
+
     jSpan = jContainer.find('span.leadspan');
   } else if (jContainer.hasClass("leadresult")) {
+
   } else if (jContainer.hasClass('leadresult')) {
 
     // pos of span.leadspan depends on arrangement (horizontal or not)
 
     // pos of span.leadspan depends on arrangement (horizontal or not)
 
     if (jContainer.get(0).className.search(/leadresult-hor/) !== -1) {
 
     if (jContainer.get(0).className.search(/leadresult-hor/) !== -1) {
 
       // result is separated by a table line.
 
       // result is separated by a table line.
       jSpan = jContainer.closest("table").find("td.leadtxt:nth-child(" + (jContainer.get(0).cellIndex + 1) + ")").find("span.leadspan");
+
       jSpan = jContainer.closest('table').find('td.leadtxt:nth-child(' + (jContainer.get(0).cellIndex + 1) + ')').find('span.leadspan');
 
     } else {
 
     } else {
       jSpan = jContainer.closest("table").find("td.leadtxt").find("span.leadspan");
+
       jSpan = jContainer.closest('table').find('td.leadtxt').find('span.leadspan');
 
     }
 
     }
 
   }
 
   }
 
   if (jSpan.length === 0) {
 
   if (jSpan.length === 0) {
     throw new jException("NotFound", {
+
     throw new jException('NotFound', {
       info: "No lead statement!",
+
       info: 'No lead statement!',
 
       jContainer: jContainer,
 
       jContainer: jContainer,
       jContainer_closest_table: jContainer.closest("table"),
+
       jContainer_closest_table: jContainer.closest('table'),
 
       leadLinkCaller: leadLinkCaller
 
       leadLinkCaller: leadLinkCaller
 
     });
 
     });
Line 819: Line 823:
 
   // Clone span; remove links, breaks, imgs
 
   // Clone span; remove links, breaks, imgs
 
   jSimplifiedLeadTxt = jSpan.clone(true);
 
   jSimplifiedLeadTxt = jSpan.clone(true);
   jSimplifiedLeadTxt.find("a").each(function () {$(this).replaceWith($(this).html()); });
+
   jSimplifiedLeadTxt.find('a').each(function () {$(this).replaceWith($(this).html()); });
   jSimplifiedLeadTxt.find("br, img").remove();
+
   jSimplifiedLeadTxt.find('br, img').remove();
 
   return jSimplifiedLeadTxt;
 
   return jSimplifiedLeadTxt;
 
}
 
}
Line 833: Line 837:
 
   try {
 
   try {
 
     var jCaller = $(caller),
 
     var jCaller = $(caller),
       jContainer = jCaller.closest("td.leadresult, th.leadtxt, td.leadtxt"), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
+
       jContainer = jCaller.closest('td.leadresult, th.leadtxt, td.leadtxt'), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
       jPlayerDiv = jContainer.closest("div.jkeyPlayer"),
+
       jPlayerDiv = jContainer.closest('div.jkeyPlayer'),
 
       jSimplifiedLeadTxt = jkeySimplifiedLeadtxt(caller), // single time this is called
 
       jSimplifiedLeadTxt = jkeySimplifiedLeadtxt(caller), // single time this is called
       jCurrHistory = jkeyHistory[jPlayerDiv.attr("id")],
+
       jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
       nextCoupletID = $.data(caller, "coupletID").next, // ref to id attribute of next couplet when used on next couplet link
+
       nextCoupletID = $.data(caller, 'coupletID').next, // ref to id attribute of next couplet when used on next couplet link
 
       firstConfirmableStep,
 
       firstConfirmableStep,
 
       jParentBlock,
 
       jParentBlock,
Line 850: Line 854:
 
       jParentBlock = jCurrHistory.getParentBlock(); // null if not nested
 
       jParentBlock = jCurrHistory.getParentBlock(); // null if not nested
 
       // in inner, nested blocks, confirming first step implies removing the try-all structure
 
       // in inner, nested blocks, confirming first step implies removing the try-all structure
       if (!jParentBlock.is(".histTable") && jCurrHistory.isFirstStepInBlockConfirmableStep()) {
+
       if (!jParentBlock.is('.histTable') && jCurrHistory.isFirstStepInBlockConfirmableStep()) {
 
         jCurrHistory.setCurrBlock(jParentBlock);
 
         jCurrHistory.setCurrBlock(jParentBlock);
 
         jCurrHistory.setCurrBlockActive(true);
 
         jCurrHistory.setCurrBlockActive(true);
Line 857: Line 861:
 
         jStep = $(firstConfirmableStep);
 
         jStep = $(firstConfirmableStep);
 
         // Is nextCoupletID identical to hash of firstConfirmableStep action link?
 
         // Is nextCoupletID identical to hash of firstConfirmableStep action link?
         firstConfItemActionLink = jStep.find("td.histStepActions a").get(0);
+
         firstConfItemActionLink = jStep.find('td.histStepActions a').get(0);
         if (firstConfItemActionLink.hash === "#" + nextCoupletID) {
+
         if (firstConfItemActionLink.hash === '#' + nextCoupletID) {
 
           // Selection in player is identical to current confirmable history action, i.e. does NOT change path.
 
           // Selection in player is identical to current confirmable history action, i.e. does NOT change path.
 
           // Execute confirm and exit jkeyDecision immediately after
 
           // Execute confirm and exit jkeyDecision immediately after
Line 867: Line 871:
 
         jCurrHistory.cleanupAfter(jStep);
 
         jCurrHistory.cleanupAfter(jStep);
 
         // empty result-div of player (no longer valid)
 
         // empty result-div of player (no longer valid)
         jPlayerDiv.children("div.jkeyResultMsg").empty().hide();
+
         jPlayerDiv.children('div.jkeyResultMsg').empty().hide();
 
       }
 
       }
 
     } else { // enable history div (disabled at start)
 
     } else { // enable history div (disabled at start)
       jPlayerDiv.find("div.jkeyHistory").show();
+
       jPlayerDiv.find('div.jkeyHistory').show();
 
       // remove all following if path has changed
 
       // remove all following if path has changed
 
       jCurrHistory.cleanupNestedBlocks();
 
       jCurrHistory.cleanupNestedBlocks();
Line 877: Line 881:
 
     jCurrHistory.createHistoryStep(
 
     jCurrHistory.createHistoryStep(
 
       jSimplifiedLeadTxt.html(),
 
       jSimplifiedLeadTxt.html(),
       jPlayerDiv.find("div.certaintyDiv input#decisionUncertain").is(":checked"),
+
       jPlayerDiv.find('div.certaintyDiv input#decisionUncertain').is(':checked'),
 
       nextCoupletID,
 
       nextCoupletID,
       $.data(caller, "coupletID").curr
+
       $.data(caller, 'coupletID').curr
 
     );
 
     );
 
     if (nextCoupletID.length) { // -> NEXT couplet
 
     if (nextCoupletID.length) { // -> NEXT couplet
Line 889: Line 893:
 
       // Prepare main result link, common names and resultqualifier (latter 2 may be missing!).
 
       // Prepare main result link, common names and resultqualifier (latter 2 may be missing!).
 
       jResult = $('<span class="leadout"/>')
 
       jResult = $('<span class="leadout"/>')
         .append(jContainer.find("span.commonnames").clone().append(" "))
+
         .append(jContainer.find('span.commonnames').clone().append(' '))
         .append(jCaller.clone().removeClass("linkbtn").unbind('click'))
+
         .append(jCaller.clone().removeClass('linkbtn').unbind('click'))
         .append(jContainer.find("span.resultqualifier").clone().prepend(" "));
+
         .append(jContainer.find('span.resultqualifier').clone().prepend(' '));
       jResult.find("br, img").remove(); // don't combine with above!
+
       jResult.find('br, img').remove(); // don't combine with above!
 
       // Add History result row (use .clone(), result already used above)
 
       // Add History result row (use .clone(), result already used above)
 
       // ## TODO: is it possible to avoid redundant span element? Text node?
 
       // ## TODO: is it possible to avoid redundant span element? Text node?
       jCurrHistory.createHistoryResult($('<span/>').append($.resource("jKey_historyResult")).append(jResult));
+
       jCurrHistory.createHistoryResult($('<span/>').append($.resource('jKey_historyResult')).append(jResult));
 
       if (!quiet) { // load into main result area
 
       if (!quiet) { // load into main result area
 
         jkeyLoadResult(jPlayerDiv, jResult);
 
         jkeyLoadResult(jPlayerDiv, jResult);
Line 916: Line 920:
 
   var eachDecisionLink = function (jCurrHistory, jParentBlock, caller, idx) {
 
   var eachDecisionLink = function (jCurrHistory, jParentBlock, caller, idx) {
 
       // create new block for current link & add it to parent block. +idx = unary operator to cast to numeric
 
       // create new block for current link & add it to parent block. +idx = unary operator to cast to numeric
       var jBlock = jCurrHistory.createBlock(false, false, $.resource("jKey_historyNested") + " " + (+idx + 1) + ":");
+
       var jBlock = jCurrHistory.createBlock(false, false, $.resource('jKey_historyNested') + ' ' + (+idx + 1) + ':');
 
       jCurrHistory.createHistoryNested(jBlock);
 
       jCurrHistory.createHistoryNested(jBlock);
 
       jCurrHistory.setCurrBlock(jBlock);
 
       jCurrHistory.setCurrBlock(jBlock);
Line 922: Line 926:
 
       jCurrHistory.setCurrBlock(jParentBlock); // revert current block to parent
 
       jCurrHistory.setCurrBlock(jParentBlock); // revert current block to parent
 
     },
 
     },
     jPlayerDiv = $(caller).closest("div.jkeyPlayer"),
+
     jPlayerDiv = $(caller).closest('div.jkeyPlayer'),
     jCurrHistory = jkeyHistory[jPlayerDiv.attr("id")],
+
     jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
 
     jNestingParent;
 
     jNestingParent;
 
   // Only if nested steps not already present:
 
   // Only if nested steps not already present:
Line 931: Line 935:
 
     jNestingParent = jCurrHistory.getCurrBlock(); // preserve current for loop
 
     jNestingParent = jCurrHistory.getCurrBlock(); // preserve current for loop
 
     // add all lead-on and lead-out links to history
 
     // add all lead-on and lead-out links to history
     jPlayerDiv.find("table.dt-body:first").find("span.leadon a, span.leadout a").each(function (idx) {
+
     jPlayerDiv.find('table.dt-body:first').find('span.leadon a, span.leadout a').each(function (idx) {
 
       eachDecisionLink(jCurrHistory, jNestingParent, this, idx);
 
       eachDecisionLink(jCurrHistory, jNestingParent, this, idx);
 
     });
 
     });
 
   }
 
   }
 
   // activate first Nested history block; mark as active in history, then load couplet or result
 
   // activate first Nested history block; mark as active in history, then load couplet or result
   jCurrHistory.firstNestedStep().find("span.histHeaderActive a").click();
+
   jCurrHistory.firstNestedStep().find('span.histHeaderActive a').click();
 
   return false; // cancel default event
 
   return false; // cancel default event
 
}
 
}
Line 946: Line 950:
 
  */
 
  */
 
function jkeySwitchHistory(caller) {
 
function jkeySwitchHistory(caller) {
   var jClosestHistory = $(caller).closest("table.histBlock, table.histTable");
+
   var jClosestHistory = $(caller).closest('table.histBlock, table.histTable');
 
   // set new & show last entry
 
   // set new & show last entry
   jkeyHistory[jClosestHistory.closest("div.jkeyPlayer").attr("id")].changeActiveBlock(jClosestHistory);
+
   jkeyHistory[jClosestHistory.closest('div.jkeyPlayer').attr('id')].changeActiveBlock(jClosestHistory);
 
   // find directly last step (excluding nested), 2x click is: revise, then confirm
 
   // find directly last step (excluding nested), 2x click is: revise, then confirm
   jClosestHistory.find("tr:first").nextAll("tr.histStep:last").find("td.histStepActions a").click().click();
+
   jClosestHistory.find('tr:first').nextAll('tr.histStep:last').find('td.histStepActions a').click().click();
 
   return false; // cancel default event
 
   return false; // cancel default event
 
}
 
}
Line 964: Line 968:
 
  */
 
  */
 
function jkeyInit() {
 
function jkeyInit() {
   var jKeys = $("div.decisiontree"),
+
   var jKeys = $('div.decisiontree'),
 
     jKeysWithCtrls,
 
     jKeysWithCtrls,
 
     fragmentID,
 
     fragmentID,
Line 972: Line 976:
 
   $.extend(true, $.jI18n, {
 
   $.extend(true, $.jI18n, {
 
     en: {
 
     en: {
       jKey_historyActive : "http://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Symbol_support_vote.svg/20px-Symbol_support_vote.svg.png",
+
       jKey_historyActive : 'http://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Symbol_support_vote.svg/20px-Symbol_support_vote.svg.png',
       jKey_historyInactive  : "http://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Symbol_partial_support_vote.svg/20px-Symbol_partial_support_vote.svg.png",
+
       jKey_historyInactive  : 'http://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Symbol_partial_support_vote.svg/20px-Symbol_partial_support_vote.svg.png',
       jKey_historyActiveTooltip : "This is the currently active history; decisions made above will append to this",
+
       jKey_historyActiveTooltip : 'This is the currently active history; decisions made above will append to this',
       jKey_historyInactiveTooltip  : "Click here to make this alternative the active history",
+
       jKey_historyInactiveTooltip  : 'Click here to make this alternative the active history',
       jKey_playerStart1st  : "Step-by-step identification",
+
       jKey_playerStart1st  : 'Step-by-step identification',
       jKey_playerStartNew  : "Start new identification",
+
       jKey_playerStartNew  : 'Start new identification',
       jKey_playerOverview  : "Key overview (printable)",
+
       jKey_playerOverview  : 'Key overview (printable)',
       jKey_playerResume  : "Resume",
+
       jKey_playerResume  : 'Resume',
       jKey_coupletContinue : "&nbsp;Continue&nbsp;",
+
       jKey_coupletContinue : '&nbsp;Continue&nbsp;',
       jKey_editorEdit  : "Edit Key",// unused
+
       jKey_editorEdit  : 'Edit Key',// unused
       jKey_editorSave  : "Save",// unused
+
       jKey_editorSave  : 'Save',// unused
       jKey_certaintyLabel  : "Flag this decision as uncertain",
+
       jKey_certaintyLabel  : 'Flag this decision as uncertain',
       jKey_certaintyTooltip : "If checkbox is activated, clicking on Next or a result (above) will flag the decision in the list of previous decisions with the word 'uncertain'.",
+
       jKey_certaintyTooltip : 'If checkbox is activated, clicking on Next or a result (above) will flag the decision in the list of previous decisions with the word ‘uncertain’.',
       jKey_tryAllAlternatives  : "Undecided: Try all alternatives",
+
       jKey_tryAllAlternatives  : 'Undecided: Try all alternatives',
       jKey_tryAllAlternativesTooltip : "If not even an uncertain decision is possible, all alternatives may be followed in parallel. After finishing the first alternative, activate the other alternatives in the history of previous decisions (below).",
+
       jKey_tryAllAlternativesTooltip : 'If not even an uncertain decision is possible, all alternatives may be followed in parallel. After finishing the first alternative, activate the other alternatives in the history of previous decisions (below).',
       jKey_mainResultMsg : "You identified: ",
+
       jKey_mainResultMsg : 'You identified: ',
       jKey_historyHeading  : "Previous decisions",
+
       jKey_historyHeading  : 'Previous decisions',
       jKey_historyConfirmable  : "Confirmable decisions:",
+
       jKey_historyConfirmable  : 'Confirmable decisions:',
       jKey_historyNested : "Alternative",
+
       jKey_historyNested : 'Alternative',
       jKey_historyResult : "Result: ",
+
       jKey_historyResult : 'Result: ',
       jKey_historyConfirm  : "confirm",
+
       jKey_historyConfirm  : 'confirm',
       jKey_historyRevise : "revise",
+
       jKey_historyRevise : 'revise',
       jKey_historyUncertainFlag  : "(uncertain)",
+
       jKey_historyUncertainFlag  : '(uncertain)',
       jKey_toolTipIsActivePath : "Currently active identification path (multiple alternatives are being followed)" // unused
+
       jKey_toolTipIsActivePath : 'Currently active identification path (multiple alternatives are being followed)' // unused
 
     },
 
     },
 
     fr: {
 
     fr: {
       jKey_playerStart1st  : "détermination interactive",
+
       jKey_playerStart1st  : 'détermination interactive',
       jKey_playerStartNew  : "détermination nouvelle",
+
       jKey_playerStartNew  : 'détermination nouvelle',
       jKey_playerOverview  : "Vue d’ensemble (imprimable)",
+
       jKey_playerOverview  : 'Vue d’ensemble (imprimable)',
       jKey_playerResume  : "continuer la détermination",
+
       jKey_playerResume  : 'continuer la détermination',
       jKey_coupletContinue : "&nbsp;avancer&nbsp;",
+
       jKey_coupletContinue : '&nbsp;avancer&nbsp;',
       jKey_editorEdit  : "éditer",
+
       jKey_editorEdit  : 'éditer',
       jKey_editorSave  : "enregistrer",
+
       jKey_editorSave  : 'enregistrer',
       jKey_certaintyLabel  : "Cette décision a marqué comme incertain",
+
       jKey_certaintyLabel  : 'Cette décision a marqué comme incertain',
       jKey_certaintyTooltip : "Si la case à cocher est activée, cliquer sur « avancer » ou un résultat (ci-dessus) marquera la décision dans la liste des décisions précédentes avec le mot « incertain ».",
+
       jKey_certaintyTooltip : 'Si la case à cocher est activée, cliquer sur « avancer » ou un résultat (ci-dessus) marquera la décision dans la liste des décisions précédentes avec le mot « incertain ».',
       jKey_tryAllAlternatives  : "Indécidable: Suivez toutes les alternatives",
+
       jKey_tryAllAlternatives  : 'Indécidable: Suivez toutes les alternatives',
       jKey_tryAllAlternativesTooltip : "Si même une décision incertaine est possible, toutes les alternatives peuvent être suivies en parallèle. Après avoir terminé la première alternative, activez les autres alternatives dans l’historique des décisions précédentes (ci-dessous).",
+
       jKey_tryAllAlternativesTooltip : 'Si même une décision incertaine est possible, toutes les alternatives peuvent être suivies en parallèle. Après avoir terminé la première alternative, activez les autres alternatives dans l’historique des décisions précédentes (ci-dessous).',
       jKey_mainResultMsg : "résultat: ",
+
       jKey_mainResultMsg : 'résultat: ',
       jKey_historyActiveTooltip : "Alternatif actif. Les décisions prises ci-dessus sont enregistrés ici",
+
       jKey_historyActiveTooltip : 'Alternatif actif. Les décisions prises ci-dessus sont enregistrés ici',
       jKey_historyInactiveTooltip  : "Cliquez ici pour poursuivre cette alternative",
+
       jKey_historyInactiveTooltip  : 'Cliquez ici pour poursuivre cette alternative',
       jKey_historyHeading  : "Décisions antérieures",
+
       jKey_historyHeading  : 'Décisions antérieures',
       jKey_historyConfirmable  : "Les décisions en cours d’examen:",
+
       jKey_historyConfirmable  : 'Les décisions en cours d’examen:',
       jKey_historyResult : "résultat: ",
+
       jKey_historyResult : 'résultat: ',
       jKey_historyConfirm  : "confirmer",
+
       jKey_historyConfirm  : 'confirmer',
       jKey_historyRevise : "vérifier",
+
       jKey_historyRevise : 'vérifier',
       jKey_historyUncertainFlag  : "(incertain)",
+
       jKey_historyUncertainFlag  : '(incertain)',
       jKey_toolTipIsActivePath : "Actuellement la route de détermination actif (plusieurs alternatives sont poursuivies)"
+
       jKey_toolTipIsActivePath : 'Actuellement la route de détermination actif (plusieurs alternatives sont poursuivies)'
 
     },
 
     },
 
     de: {
 
     de: {
       jKey_playerStart1st  : "Interaktive Bestimmung",
+
       jKey_playerStart1st  : 'Interaktive Bestimmung',
       jKey_playerStartNew  : "Neue Bestimmung",
+
       jKey_playerStartNew  : 'Neue Bestimmung',
       jKey_playerOverview  : "Übersicht (druckbar)",
+
       jKey_playerOverview  : 'Übersicht (druckbar)',
       jKey_playerResume  : "Bestimmung fortsetzen",
+
       jKey_playerResume  : 'Bestimmung fortsetzen',
       jKey_coupletContinue : "&nbsp;Weiter&nbsp;",
+
       jKey_coupletContinue : '&nbsp;Weiter&nbsp;',
       jKey_editorEdit  : "Bearbeiten",
+
       jKey_editorEdit  : 'Bearbeiten',
       jKey_editorSave  : "Speichern",
+
       jKey_editorSave  : 'Speichern',
       jKey_certaintyLabel  : "Diese Entscheidung als unsicher kennzeichnen",
+
       jKey_certaintyLabel  : 'Diese Entscheidung als unsicher kennzeichnen',
       jKey_certaintyTooltip : "Wenn die Checkbox aktiviert ist, wird beim Klick auf 'Weiter' oder ein Ergebnis diese Entscheidung in der Liste bisheriger Entscheidungen mit dem Wort 'unsicher' gekennzeichnet.",
+
       jKey_certaintyTooltip : 'Wenn die Checkbox aktiviert ist, wird beim Klick auf ‚Weiter‘ oder ein Ergebnis diese Entscheidung in der Liste bisheriger Entscheidungen mit dem Wort ‚unsicher‘ gekennzeichnet.',
       jKey_tryAllAlternatives  : "Nicht entscheidbar: Verfolge alle Alternativen",
+
       jKey_tryAllAlternatives  : 'Nicht entscheidbar: Verfolge alle Alternativen',
       jKey_tryAllAlternativesTooltip : "Wenn überhaupt keine Entscheidung möglich ist, können die Alternativen parallel verfolgt werden. Nachdem die erste Alternative zu Ende geführt wurde, können die Übrigen in der Liste der bisherigen Entscheidungen (unten) aktiviert werden.",
+
       jKey_tryAllAlternativesTooltip : 'Wenn überhaupt keine Entscheidung möglich ist, können die Alternativen parallel verfolgt werden. Nachdem die erste Alternative zu Ende geführt wurde, können die Übrigen in der Liste der bisherigen Entscheidungen (unten) aktiviert werden.',
       jKey_mainResultMsg : "Ergebnis: ",
+
       jKey_mainResultMsg : 'Ergebnis: ',
       jKey_historyActiveTooltip : "Aktive Alternative. Die oben getroffenen Entscheidungen werden hier aufgezeichnet",
+
       jKey_historyActiveTooltip : 'Aktive Alternative. Die oben getroffenen Entscheidungen werden hier aufgezeichnet',
       jKey_historyInactiveTooltip  : "Klicken Sie hier, um diese Alternative weiter zu verfolgen",
+
       jKey_historyInactiveTooltip  : 'Klicken Sie hier, um diese Alternative weiter zu verfolgen',
       jKey_historyHeading  : "Bisherige Entscheidungen",
+
       jKey_historyHeading  : 'Bisherige Entscheidungen',
       jKey_historyConfirmable  : "Entscheidungen in Überprüfung:",
+
       jKey_historyConfirmable  : 'Entscheidungen in Überprüfung:',
       jKey_historyResult : "Ergebnis: ",
+
       jKey_historyResult : 'Ergebnis: ',
       jKey_historyConfirm  : "bestätigen",
+
       jKey_historyConfirm  : 'bestätigen',
       jKey_historyRevise : "überprüfen",
+
       jKey_historyRevise : 'überprüfen',
       jKey_historyUncertainFlag  : "(unsicher)",
+
       jKey_historyUncertainFlag  : '(unsicher)',
       jKey_toolTipIsActivePath : "Derzeit aktiver Bestimmungsweg (mehrere Alternativen werden verfolgt)"
+
       jKey_toolTipIsActivePath : 'Derzeit aktiver Bestimmungsweg (mehrere Alternativen werden verfolgt)'
 
     },
 
     },
 
     it: {
 
     it: {
       jKey_playerStart1st  : "Esegui passo-dopo-passo",
+
       jKey_playerStart1st  : 'Esegui passo-dopo-passo',
       jKey_playerStartNew  : "Nuova identificazione",
+
       jKey_playerStartNew  : 'Nuova identificazione',
       jKey_playerOverview  : "Sintesi completa (stampabile)",
+
       jKey_playerOverview  : 'Sintesi completa (stampabile)',
       jKey_playerResume  : "Ricomincia l’identificazione",
+
       jKey_playerResume  : 'Ricomincia l’identificazione',
       jKey_coupletContinue : "&nbsp;Continua&nbsp;",
+
       jKey_coupletContinue : '&nbsp;Continua&nbsp;',
       jKey_editorEdit  : "Modifica",
+
       jKey_editorEdit  : 'Modifica',
       jKey_editorSave  : "Salva",
+
       jKey_editorSave  : 'Salva',
       jKey_certaintyLabel  : "Segna scelta come insicura", //REVISE
+
       jKey_certaintyLabel  : 'Segna scelta come insicura', //REVISE
       jKey_certaintyTooltip : "(click, then make your next decision)", //TRANSLATE
+
       jKey_certaintyTooltip : '(click, then make your next decision)', //TRANSLATE
       jKey_mainResultMsg : "Il risultato dell'identificazione è: ",
+
       jKey_mainResultMsg : 'Il risultato dell’identificazione è: ',
       jKey_historyHeading  : "Scelte precedenti",
+
       jKey_historyHeading  : 'Scelte precedenti',
       jKey_historyConfirmable  : "Scelta confermabile:",
+
       jKey_historyConfirmable  : 'Scelta confermabile:',
       jKey_historyResult : "Risultato: ",
+
       jKey_historyResult : 'Risultato: ',
       jKey_historyConfirm  : "conferma",
+
       jKey_historyConfirm  : 'conferma',
       jKey_historyRevise : "correggi",
+
       jKey_historyRevise : 'correggi',
       jKey_historyUncertainFlag  : "(incerta)",
+
       jKey_historyUncertainFlag  : '(incerta)',
       jKey_toolTipIsActivePath : "Percorso di identificazione attualmente attivo (vengono seguite alternative multiple)"
+
       jKey_toolTipIsActivePath : 'Percorso di identificazione attualmente attivo (vengono seguite alternative multiple)'
 
     }
 
     }
 
   });
 
   });
 
   if (jKeys.length) { // only if at least one key exists
 
   if (jKeys.length) { // only if at least one key exists
     $("head").append(
+
     $('head').append(
       "<style type=\"text/css\">"
+
       '<style type="text/css">'
 
         // Canvas: top padding needed for IE, FF/Chrome could do without
 
         // Canvas: top padding needed for IE, FF/Chrome could do without
         + "div.jkeyCanvas {background-color:#FFFFFF; border:5px solid #FFC51A; padding:0.7em 1em 1em 1em; color:rgba(0,0,0,1.0);}\n"
+
         + 'div.jkeyCanvas {background-color:#FFFFFF; border:5px solid #FFC51A; padding:0.7em 1em 1em 1em; color:rgba(0,0,0,1.0);}\n'
 
         // do dim background around the jkeyCanvas, applied to body, links, etc.
 
         // do dim background around the jkeyCanvas, applied to body, links, etc.
         + ".jkdimmed {color:rgba(0,0,0,0.2) !important;}\n"
+
         + '.jkdimmed {color:rgba(0,0,0,0.2) !important;}\n'
 
         // Safari 4 has problems with 100% table width:
 
         // Safari 4 has problems with 100% table width:
         + ( ( navigator.userAgent.toUpperCase().indexOf('SAFARI') > 0 && parseFloat(navigator.appVersion) < 5 ) ? "table.dt-caption {width:98%}\n" : "")
+
         + ( ( navigator.userAgent.toUpperCase().indexOf('SAFARI') > 0 && parseFloat(navigator.appVersion) < 5 ) ? 'table.dt-caption {width:98%}\n' : '')
         + "div.jkeyControls a {vertical-align: middle;}\n"
+
         + 'div.jkeyControls a {vertical-align: middle;}\n'
         + "div.jkeyPlayer table.dt-body {margin:0.5em}\n"
+
         + 'div.jkeyPlayer table.dt-body {margin:0.5em}\n'
         + "div.jkeyResultMsg {clear:right; margin:0 0 1em; padding:1em; font-weight:bold; background-color:#EEF0F0; border:1px solid #444444; }\n"
+
         + 'div.jkeyResultMsg {clear:right; margin:0 0 1em; padding:1em; font-weight:bold; background-color:#EEF0F0; border:1px solid #444444; }\n'
         + "div.jkeyResultMsg span.leadout, div.jkeyResultMsg span.commonnames {background-color:transparent}\n"
+
         + 'div.jkeyResultMsg span.leadout, div.jkeyResultMsg span.commonnames {background-color:transparent}\n'
         + "div.certaintyDiv {margin:1.8em 0 1em 0; padding:0.8em 0.2em; font-size:83%; color:#555;}\n"
+
         + 'div.certaintyDiv {margin:1.8em 0 1em 0; padding:0.8em 0.2em; font-size:83%; color:#555;}\n'
         + "input[type=checkbox] {vertical-align:middle;}\n" /* MAKE GENERIC?? */
+
         + 'input[type=checkbox] {vertical-align:middle;}\n' /* MAKE GENERIC?? */
         + "div.jkeyHistory {margin-top:1em;}\n"
+
         + 'div.jkeyHistory {margin-top:1em;}\n'
         + "table.histTable, table.histBlock {background-color:transparent;}\n"
+
         + 'table.histTable, table.histBlock {background-color:transparent;}\n'
         + "table.histBlock {border-left:1px solid; width:100%;}\n"
+
         + 'table.histBlock {border-left:1px solid; width:100%;}\n'
         + "td.histStepNumber, td.histNestedEmpty, td.histResultSymbol {width:1em; padding:0 0.6em;}\n"
+
         + 'td.histStepNumber, td.histNestedEmpty, td.histResultSymbol {width:1em; padding:0 0.6em;}\n'
         + "td.histHeaderContent, td.histConfirmSubhdgContent {padding-left:0.6em; font-weight:bold;}\n"
+
         + 'td.histHeaderContent, td.histConfirmSubhdgContent {padding-left:0.6em; font-weight:bold;}\n'
         + "td.histStepActions {text-align:center; width:50px; padding-left:1em; }\n"
+
         + 'td.histStepActions {text-align:center; width:50px; padding-left:1em; }\n'
         + "td.histStepNumber, td.histStepActions, td.histResultSymbol {vertical-align:top;}\n"
+
         + 'td.histStepNumber, td.histStepActions, td.histResultSymbol {vertical-align:top;}\n'
         + "span.histStepCertainty {background-color:#FFA07A;}\n"
+
         + 'span.histStepCertainty {background-color:#FFA07A;}\n'
         + "div.jkeyPlayer table.dt-body td.dt-nodeid, div.jkeyPlayer table.dt-body th.leadtxt, div.jkeyPlayer table.dt-body td.leadtxt, div.jkeyPlayer table.dt-body td.leadresult {padding-top:1em;line-height:1.7em}\n"
+
         + 'div.jkeyPlayer table.dt-body td.dt-nodeid, div.jkeyPlayer table.dt-body th.leadtxt, div.jkeyPlayer table.dt-body td.leadtxt, div.jkeyPlayer table.dt-body td.leadresult {padding-top:1em;line-height:1.7em}\n'
         + "</style>"
+
         + '</style>'
 
     );
 
     );
 
     // flag: hide top metadata display (geoscope, creator, etc.)
 
     // flag: hide top metadata display (geoscope, creator, etc.)
     jKeys.filter(".jkey-hidekeymetadata").find("span.collapseButton:first a").click();
+
     jKeys.filter('.jkey-hidekeymetadata').find('span.collapseButton:first a').click();
     // flag: show interactive key controls only for keys without class "jkey-nocontrols"
+
     // flag: show interactive key controls only for keys without class 'jkey-nocontrols'
     jKeysWithCtrls = jKeys.not(".jkey-nocontrols");
+
     jKeysWithCtrls = jKeys.not('.jkey-nocontrols');
     // add player control + always add "show-all-extras" checkbox (toggleAllExtras) in Overview mode (later removed)
+
     // add player control + always add 'show-all-extras' checkbox (toggleAllExtras) in Overview mode (later removed)
 
     jKeysWithCtrls
 
     jKeysWithCtrls
       .find(".dt-caption")
+
       .find('.dt-caption')
 
       .prepend('<div class="jkeyControls" style="float:right;text-align:right; background-color:transparent; font-weight:bold; margin-bottom:6px"><span class="jkeyPlayerStart1st nowrap linkbtn" style="padding:4px 6px;">'
 
       .prepend('<div class="jkeyControls" style="float:right;text-align:right; background-color:transparent; font-weight:bold; margin-bottom:6px"><span class="jkeyPlayerStart1st nowrap linkbtn" style="padding:4px 6px;">'
         + $.imglinkBuilder("jKey_iconStart1st", "jKey_playerStart1st", "onclick='return jkeySetMode(\"firststart\",this);'")
+
         + $.imglinkBuilder('jKey_iconStart1st', 'jKey_playerStart1st', 'onclick="return jkeySetMode(\'firststart\',this);"')
 
         + '</span><span class="jkeyToggleAllExtras nowrap"><br/><input type="checkbox" id="toggleAllExtras" class="toggleAllExtras" value="1" onclick="$.toggleAllCollapsible(this.checked, this)" onkeyup="$.toggleAllCollapsible(this.checked, this)" />'
 
         + '</span><span class="jkeyToggleAllExtras nowrap"><br/><input type="checkbox" id="toggleAllExtras" class="toggleAllExtras" value="1" onclick="$.toggleAllCollapsible(this.checked, this)" onkeyup="$.toggleAllCollapsible(this.checked, this)" />'
         + ' <label for="toggleAllExtras" style="font-weight:normal;font-size:83%">' + $.resource("jKey_expandAll")
+
         + ' <label for="toggleAllExtras" style="font-weight:normal;font-size:83%">' + $.resource('jKey_expandAll')
 
         + '</label><br/></span><span class="jkeyPlayerOverview nowrap">' +
 
         + '</label><br/></span><span class="jkeyPlayerOverview nowrap">' +
         $.imglinkBuilder("jKey_iconOverview", "jKey_playerOverview", "onclick='return jkeySetMode(\"overview\",this);'")
+
         $.imglinkBuilder('jKey_iconOverview', 'jKey_playerOverview', 'onclick="return jkeySetMode(\'overview\',this);"')
 
         + '<br/></span><span class="jkeyPlayerResume nowrap">'
 
         + '<br/></span><span class="jkeyPlayerResume nowrap">'
         + $.imglinkBuilder("jKey_iconResume", "jKey_playerResume", "onclick='return jkeySetMode(\"resumed\",this);'")
+
         + $.imglinkBuilder('jKey_iconResume', 'jKey_playerResume', 'onclick="return jkeySetMode(\'resumed\',this);"')
 
         + '<br/></span><span class="jkeyPlayerStartNew nowrap">'
 
         + '<br/></span><span class="jkeyPlayerStartNew nowrap">'
         + $.imglinkBuilder("jKey_iconStartNew", "jKey_playerStartNew", "onclick='return jkeySetMode(\"newstart\",this);'") +
+
         + $.imglinkBuilder('jKey_iconStartNew', 'jKey_playerStartNew', 'onclick="return jkeySetMode(\'newstart\',this);"') +
 
         '</span></div>')
 
         '</span></div>')
       .find(".jkeyPlayerOverview,.jkeyPlayerResume,.jkeyPlayerStartNew").hide(); // RETEST WHETHER DIRECT HIDE/DISPLAY:NONE NOW POSSIBLE! putting display:none causes layout problems when showing them later!
+
       .find('.jkeyPlayerOverview,.jkeyPlayerResume,.jkeyPlayerStartNew').hide(); // RETEST WHETHER DIRECT HIDE/DISPLAY:NONE NOW POSSIBLE! putting display:none causes layout problems when showing them later!
 
     // Evaluate URL-hash-based and div-class-based autostart options:
 
     // Evaluate URL-hash-based and div-class-based autostart options:
 
     fragmentID = document.location.hash;
 
     fragmentID = document.location.hash;
     if (fragmentID.length > 1) { // is non-empty hash (>1 to ignore "#" itself):
+
     if (fragmentID.length > 1) { // is non-empty hash (>1 to ignore '#' itself):
       jKeyAutostartPos = fragmentID.indexOf("jkey-autostart");
+
       jKeyAutostartPos = fragmentID.indexOf('jkey-autostart');
       if (jKeyAutostartPos > -1) { // test for presence of "jkey-autostart" inside hash
+
       if (jKeyAutostartPos > -1) { // test for presence of 'jkey-autostart' inside hash
 
         fragmentID = fragmentID.substring(0, jKeyAutostartPos); // remove jkey-autostart
 
         fragmentID = fragmentID.substring(0, jKeyAutostartPos); // remove jkey-autostart
         if (fragmentID.length === 1) { // just the "#"
+
         if (fragmentID.length === 1) { // just the '#'
 
           isKeyRef = false;
 
           isKeyRef = false;
 
         } else {
 
         } else {
Line 1,120: Line 1,124:
 
         }
 
         }
 
         if (isKeyRef) { // existing ID, start this key (multiple keys may exist)
 
         if (isKeyRef) { // existing ID, start this key (multiple keys may exist)
           jkeySetMode("firststart", fragmentID);
+
           jkeySetMode('firststart', fragmentID);
 
         } else { // else start first key
 
         } else { // else start first key
           jkeySetMode("firststart", jKeys.get(0));
+
           jkeySetMode('firststart', jKeys.get(0));
 
         }
 
         }
 
       }
 
       }
 
     } else { // no URL-based autostart -> check for flag: jkey-autostart -> automatically change to interactive mode
 
     } else { // no URL-based autostart -> check for flag: jkey-autostart -> automatically change to interactive mode
       jKeys.filter(".jkey-autostart").each(function () {
+
       jKeys.filter('.jkey-autostart').each(function () {
         jkeySetMode("firststart", "#" + this.id);
+
         jkeySetMode('firststart', '#' + this.id);
 
       });
 
       });
 
     }
 
     }

Revision as of 13:56, 24 August 2017

/*** TEMP NOTE: current class flags: jkey-hidekeymetadata (TEST), jkey-nocontrols (OK) jkey-autostart (FAIL nested keys) jkey-simplified (NEEDS TESTING) ***/
/**
 * @description jKey.js - Copyright (c) 2009-2012 Stephan Opitz, Andreas Plank & G. Hagedorn, JKI Berlin Dahlem
 * This program is free software; you can redistribute it and/or modify it under the terms of the EUPL v.1.1
 * or (at your option) the GNU General Public License as published by the Free Software Foundation; either
 * GPL v.3 or (at your option) any later version. This program is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License (http://www.gnu.org/licenses/) for more details.
 * @requires: $.imglinkBuilder
 * @requires: $.linkBuilder
 * @requires: $.random
 * @requires: $.toggleAllCollapsible
 * TODO add documentation
 */
/* Settings for JSLint: */
/*jslint sloppy:false, maxerr:100, indent:2 */
/*global $, jQuery, document, console, window, alert, wgPageName, wgServer, wgScript, wgUserName, jedtInit */

// set ECMAScript 5 Strict Mode. Done globally, not within each function. Later the whole jKey should be wrapped as a gadget providing scope for this
'use strict';

////////////////////////
// Exception handling //
////////////////////////

/**
 * @description: Exception constructor, providing toString method
 * @param {string} errorCode  identifying errors
 * @param {object} variables optional for detailed information
 * @returns {jException}
 */
function jException(errorCode, variables) {
  this.errorCode = errorCode;
  this.variables = variables;
}
/**
 * @augments jException()
 * @returns {String}
 */
jException.prototype.toString = function () {
  var message = 'JKey Exception ' + this.errorCode + '\n\n',
   key;
  for (key in this.variables) {
    // only own properties, not inherited ones:
    if (this.variables.hasOwnProperty(key)) {
      message += ' ' + key + ': ' + this.variables[key] + '\n';
    }
  }
  return message;
};
// also override toString of default Error constructor
Error.prototype.toString = function () {
  return 'Javascript exception: ' + this.name 
    + '\n\nMessage: ' + this.message 
    + '\nFileName: ' + this.fileName 
    + '\nLineNumber: ' + this.lineNumber;
};
/*
 * @description report exception to console or alert box.
 * exception: jException or JS internal exception
 */
function jExceptionAlert(exception) {
  if (window.console) { // IE dev.tools (=F12), or FF firebug console ENABLED
    console.log(exception); // TODO: in ie watch-console there is no debug fct?!
  } else { // perhaps in FF write to browser-javascript-console: throw new Error('text');
    alert(exception);
  }
}

/////////////////////////
// JKey player history //
/////////////////////////

/**
 * @description: history header CONSTRUCTOR
 * 
 * @returns {HistoryHeader}
 */
function HistoryHeader() {
 /**
  * @description: creates new header for history of previous decisions
  * 
  * @requires $.resource()
  * @param {boolean} isActiveHistory boolean is history set as active
  * @param {boolean} isFirstHistory hides active-history-flag if true
  * @param {string} newContent the new description
  * @returns {unresolved}
  */
  this.create = function (isActiveHistory, isFirstHistory, newContent) {
    // create base header
    this.item = $('<tr class="histHeader"/>')
      .append($('<td class="histHeaderContent" colspan="3"/>').html((newContent ? newContent : $.resource('jKey_historyHeading')) + ' &nbsp; ')
        .append($('<span class="histHeaderActive"/>')
          .append($.imglinkBuilder('jKey_historyActive', '', 'class="histActiveOn" onclick="return jkeySwitchHistory(this);"'))
          .toggle(!isFirstHistory)
          ));
    this.setCurrBlockActive(isActiveHistory);
    return this.item;
  };

  // item specific fields
  /*
  * Description: get "active" state; return boolean
  */
  this.isActive = function () {
    return this.item.find('span.histHeaderActive a').hasClass('histActiveOn');
  };
  /*
  * Description: set the active history flag
  * isActiveHistory: boolean
  */
  this.setCurrBlockActive = function (isActiveHistory) { // do not use imglinkBuilder, replaceWith kills layout
    var histHeaderActiveCell = this.item.find('span.histHeaderActive a');
    // change class & image
    histHeaderActiveCell.attr({'class': (isActiveHistory ? 'histActiveOn' : 'histActiveOff')});
    histHeaderActiveCell.find('img')
     .attr('src', $.resource(isActiveHistory ? 'jKey_historyActive' : 'jKey_historyInactive'))
     .attr('title', $.resource(isActiveHistory ? 'jKey_historyActiveTooltip' : 'jKey_historyInactiveTooltip'));
  };
}// HistoryHeader()
/*
 * Description: history confirm subheading CONSTRUCTOR
 */
function HistoryConfirmSubheading() {
  /* Description: create new subheading with newContent string */
  this.create = function (newContent) {
    this.item = $('<tr class="histConfirmSubhdg"/>')
      .append($('<td colspan="3" class="histConfirmSubhdgContent"/>').html(newContent));
    return this.item;
  };
}
/**
 * Description: history result CONSTRUCTOR
 * 
 * @returns {HistoryResult}
 */
function HistoryResult() {
  /* Description: create new result item with newContent string */
  this.create = function (newContent) {
    this.item = $('<tr class="histResult"/>')
      .append($('<td class="histResultSymbol"/>').text('►'))
      .append($('<td colspan="2" class="histResultContent"/>').html(newContent));
    return this.item;
  };
}

/*
 * Description: history nested CONSTRUCTOR
 * @returns {HistoryNested}
 */
function HistoryNested() {
  /*
  * newContent: blocks with alternative paths
  */
  this.create = function (newContent) {
    // create base step
    this.item = $('<tr class="histNested"/>')
      .append($('<td class="histNestedEmpty"/>'))
      .append($('<td class="histNestedContent" colspan="2"/>').html(newContent));
    return this.item;
  };
}
/**
 * Description: history step CONSTRUCTOR
 * @returns {HistoryStep}
 */
function HistoryStep() {
  /*
  * Description: creates new step
  * newStepNumber: step number
  * newContent: description of previous decision
  * isUncertain: flag whether decision was uncertain
  * confCoupletID: id of confirm-couplet
  * revCoupletID: id of revise-couplet
  */
  this.create = function (newStepNumber, newContent, isUncertain, confCoupletID, revCoupletID) {
    // create base step
    this.item = $('<tr class ="histStep"/>')
      .append($('<td class="histStepNumber"/>').text(newStepNumber + '.'))
      .append('<td class="histStepContent"/>')
    // Create revise-history action (changable to confirm).
      .append(
        $('<td class="histStepActions"/>')
        // (side-requirement: href MUST be confCoupletID:)
          .append(
            $.linkBuilder(
              'jKey_historyRevise',
              '',
              '#' + confCoupletID,
              ' class="histStepActionRevise small-linkbtn" onclick="return jkeyHistoryAction(this, \'' + revCoupletID + '\', \'' + confCoupletID + '\');"'
            )
          )
      );
    this.setContent(newContent);
    this.setUncertainty(isUncertain);
    return this.item;
  };
  /*
  * Description: set decision certainty of history step
  * isUncertain: true -> display marker text
  */
  this.setUncertainty = function (isUncertain) {
    this.item.find('td.histStepContent span.histStepCertainty')
      .html(isUncertain ? ('&nbsp;' + $.resource('jKey_historyUncertainFlag') + '&nbsp;') : '');
  };
  /*
  * Description: return previously recorded uncertainty for a history step (boolean)
  */
  this.getUncertainty = function () {
    return this.item.find('td.histStepContent span.histStepCertainty').text().length > 0;
  };
  /*
  * set the step content (override inheritance)
  * @param newContent: the new html value
  */
  this.setContent = function (newContent) {
    this.item.find('td.histStepContent')
      .empty()
      .append(newContent)
      .append('<span class="histStepCertainty"/>');
  };
  /*
  * Description: get number in front of step
  */
  this.getStepNumber = function () {
    return parseInt(this.item.find('td.histStepNumber').text(), 10); // parseInt example: " 8." will return 8
  };
  /*
  * Description: set confirm/revise action
  * setToConfirm: the new action state (true = confirm active, false = revise active)
  */
  this.setConfirmable = function (setToConfirm) {
    this.item.find('td.histStepActions a')
      .attr({'class': (setToConfirm ? 'histStepActionConfirm small-linkbtn' : 'histStepActionRevise small-linkbtn')})
      .text($.resource((setToConfirm ? 'jKey_historyConfirm' : 'jKey_historyRevise')));
  };
  /*
  * Description: check whether action is confirm or revise
  */
  this.isConfirmable = function () {
    return this.item.find('td.histStepActions a').is('.histStepActionConfirm');
  };
}

// global player history object, one key/history pair for each identification key on the html page
var jkeyHistory = {};
/*
 * Description: player history CONSTRUCTOR
 */
function PlayerHistory(jPlayerDiv) {
  // local variables: item types = histHeader, histStep, histSubkeyHdg, histResult, histBlock;
  // no more lazy loading for header (used immediately!), and historyResult (small)
  var jCurrHistBlock,
    historyHeader = new HistoryHeader(),
    historyConfirmSubheading = new HistoryConfirmSubheading(),
    historyResult = new HistoryResult(),
    historyNested = new HistoryNested(),
    historyStep, // LAZY LOADING
  /*
  * Description: ensure that static class historyStep is loaded (lazy loading, deferring until used)
  */
    histStep_lazyLoad = function () {
      if (historyStep === undefined) {historyStep = new HistoryStep(); }
    };
  /*
  * Description: create new history section in DOM tree, together with header row.
  * active: true = set as active history
  * isFirstHistory: if set hides active-history-flag
  */
  this.createBlock = function (active, isFirstHistory, newContent) {
    return $('<table cellpadding="0" cellspacing="0" class="' + (isFirstHistory ? 'histTable' : 'histBlock') + '"/>')
      .append('<tr class="histLayout"/><td/><td/><td/></tr>')
      .append(
        historyHeader.create(
          active,
          isFirstHistory,
          '<b>' + (newContent) ? newContent : $.resource('jKey_historyHeading') + '</b>'
        )
      );
  };
  /*
  * Description: add an item to historyTable
  */
  this.addItem = function (item) {
    jCurrHistBlock.append(item);
  };
  /*
  * Description: change active history
  */
  this.changeActiveBlock = function (newActiveBlock) {
    this.setCurrBlockActive(false); // deactivate current block
    this.setCurrBlock(newActiveBlock); // set new
    this.setCurrBlockActive(true); // activate new
  };
  /*
  * Description: get history block
  */
  this.getCurrBlock = function () {
    return jCurrHistBlock;
  };
  /*
  * Description: set history block
  */
  this.setCurrBlock = function (newBlock) {
    jCurrHistBlock = newBlock;
  };
  /*
  * Description: first item step visible confirm link
  * historyTable: history ref
  * return: item object or null if not found
  */
  this.getfirstConfirmableStep = function () {
    var retValue = null,
      jAllSteps = jCurrHistBlock.find('tr:first').nextAll('tr.histStep'),
      parentThis;
    if (jAllSteps.length) { // steps exist
      parentThis = this;
      jAllSteps.each(function () {
        if ((retValue === null) && parentThis.withHistoryStep(this).isConfirmable()) {
          retValue = this;
        }
      });
    }
    return retValue;
  };
  /*
  * Description: change all history action links in history table
  * (revisable before, confirmable starting at firstConfirmableItem)
  * historyTable: history ref
  * firstConfirmableItem: first confirmable item after updating
  */
  this.updateConfirmability = function (firstConfirmableItem) {
    var itemFound = false,
      // get steps within block, not including nested steps
      jAllSteps = jCurrHistBlock.find('tr:first').nextAll('tr.histStep'),
      parentThis;
    if (jAllSteps.length) { // entries exists
      parentThis = this;
      jAllSteps.each(function () {
        if (this === firstConfirmableItem) {
          itemFound = true; // true once item was passed in loop
          // add confirm subheading in front of confirmable steps
          $(this).before(parentThis.createHistoryConfirmSubheading('<i>' + $.resource('jKey_historyConfirmable') + '</i>'));
        }
        // update history actions
        parentThis.withHistoryStep(this).setConfirmable(itemFound);
      }); // TODO: parentThis and itemFound create CLOSUREs - change code?
    }
  };
  /*
  * Description: handle history path changes (cleanup of obsolete steps)
  */
  this.cleanupAfter = function (jStep) {
    // rework history if decision path has changed. Remove confirm-sub-heading, item itself & following siblings
    jStep.prevAll('tr.histConfirmSubhdg').remove();
    jStep.nextAll('tr').andSelf().remove();
  };
  this.cleanupNestedBlocks = function () {
    // find last normal history step (or history header; fallback if no steps exists, e.g. when using try-all as first decision)
    var jStep = jCurrHistBlock.find('tr:first').nextAll('tr.histHeader, tr.histStep').filter(':last');
    if (jStep.length) {
      jStep.nextAll('tr').remove();
    }
  };
  this.cleanupConfirmableSteps = function () {
    var firstConfirmableStep = this.getfirstConfirmableStep();
    if (firstConfirmableStep) {
      this.cleanupAfter($(firstConfirmableStep));
    }
  };

  /*
  * Description: create a history sub-heading, nested block, or result history table row
  * newContent: content for item
  */
  this.createHistoryConfirmSubheading = function (newContent) {
    return historyConfirmSubheading.create(newContent); // this.addItem( because will be added between block items
  };
  this.createHistoryNested = function (newContent) {
    this.addItem(historyNested.create(newContent).get(0));
  };
  this.createHistoryResult = function (newContent) {
    this.addItem(historyResult.create(newContent).get(0));
  };
  /*
  * Description: creates a history step item
  */
  this.createHistoryStep = function (newContent, isUncertain, confCoupletID, revCoupletID) {
    histStep_lazyLoad();
    this.addItem(historyStep.create(this.getNewStepNumber(), newContent, isUncertain, confCoupletID, revCoupletID).get(0));
  };
  /*
  * Description: return history step class initialized with item
  */
  this.withHistoryStep = function (item) {
    histStep_lazyLoad();
    historyStep.item = $(item);
    return historyStep;
  };
  // item specific fields
  /*
  * Description: get "active" state; return boolean
  */
  this.isActive = function () {
    historyHeader.item = jCurrHistBlock.find('tr.histHeader:first');
    return historyHeader.isActive();
  };
  /*
  * Description: set the active history flag
  * isActiveHistory: boolean
  */
  this.setCurrBlockActive = function (newIsActive) {
    historyHeader.item = jCurrHistBlock.find('tr.histHeader:first');
    historyHeader.setCurrBlockActive(newIsActive);
  };
  /*
  * Description: get next available number for a history step in a history block (last + 1).
  * If the block is nested and has no steps yet, outer blocks are taken into account.
  * historyBlock : a jquery-history-block, defaults to jCurrHistBlock if omitted
  */
  this.getNewStepNumber = function (historyBlock) {
    if (!historyBlock) {
      historyBlock = jCurrHistBlock;
    }
    var stepNumber = 0,
      jLastStep = historyBlock.find('tr:first').nextAll('tr.histStep:last');
    if (jLastStep.length) {
      stepNumber = this.withHistoryStep(jLastStep.get(0)).getStepNumber();
    } else if (!historyBlock.is('.histTable')) { // unless outermost and not step (return 0): recurse to parent
      historyBlock = this.getParentBlock();
      return this.getNewStepNumber(historyBlock);
    }
    return stepNumber + 1;
  };
  /*
  * Description: checks whether first step has to be confirmed
  */
  this.isFirstStepInBlockConfirmableStep = function () {
    var firstStep = jCurrHistBlock.find('tr:first').nextAll('tr.histStep:first');
    return (firstStep.prev('tr').hasClass('histConfirmSubhdg'));
  };
  /*
  * Description: retrieve first nested block within current history block
  */
  this.firstNestedStep = function () {
    return jCurrHistBlock.find('tr:first').nextAll('tr.histNested:first');
  };
  /*
  * Description: get parent block of current block (as $ object; if already outermost -> getParentBlock.length=0)
  */
  this.getParentBlock = function () {
    // first closest('table.histBlock, table.histTable') will find own block, then up and find parent:
    var jBlock = jCurrHistBlock.closest('table.histBlock, table.histTable');
    if (!jBlock.is('.histTable')) { // unless already outermost
      jBlock = jBlock.parent().closest('table.histBlock, table.histTable');
    }
    return jBlock;
  };

  // Constructor logic - has to be at end of obj/class definition
  jCurrHistBlock = this.createBlock(true, true); // first and (here in constructor so far) only one
  jPlayerDiv.append(
    $('<div class="jkeyHistory dt-box"/>').hide()
      .append(jCurrHistBlock)
  );
}

/////////////////
// JKey player //
/////////////////

/*
 * Description: is jRefTarget a valid location inside a key?
 * jRefTarget: an idref as jquery object
 * return: boolean
 */
function jkeyisKeyRef(jRefTarget) {
  return (jRefTarget.is('td.dt-nodeid') || jRefTarget.is('tr.dt-row') || jRefTarget.is('div.decisiontree'));
}

/*
 * Description: transform all rows of couplet
 * jPlayerDiv: main div around player (unused?)
 * jDecisionRow: first row of a couplet - must be tested prior to calling this!
 * jPlayerCouplet: position where couplet will appended
 * @todo parentlead does lead to jDecisionRow.length === 0
 */
function jkeyTransformCouplet(jPlayerDiv, jDecisionRow, jPlayerCouplet) {
  if (jDecisionRow.length === 0) {
    throw new jException('NotFound', {
      info: 'Transform w/o valid jDecisionRow'
    });
  }
  // row id is like id="Lz_1_row"; we need the id of first td inside: id="Lz_1"
  var currCoupletID = jDecisionRow.children('td[id]:first').attr('id'),
    jNextCouplet,
    eachLeadout = function () {// this = a leadout link
      var nextCoupletID = this.hash,
        isInternalLink = ((nextCoupletID.length > 0) && (this.href.search('/' + wgPageName + '#') !== -1)),
        jKeyTable,
        jNodeID;
      // Example for test above: wgPageName="ThisPage", this.href "http://.../AlsoThisPageTwo#xxx" -> must include / and #
      if (isInternalLink) {
        jNextCouplet = $(nextCoupletID);
        // Check if valid element within a player was found
        isInternalLink = jkeyisKeyRef(jNextCouplet);
        if (isInternalLink) {
          if (jNextCouplet.is('td.dt-nodeid')) { // already is the target node-id
            nextCoupletID = jNextCouplet.attr('id');
          } else { // might be row or div; get key div (closest finds itself), then table, then dt-nodeid
            // jKeyTable is not loop-invariable; a wiki page may have multiple keys!
            jKeyTable = jNextCouplet.closest('div.decisiontree').find('table.dt-body:last');
            jNodeID = jKeyTable.find('td.dt-nodeid:first');
            isInternalLink = (jNodeID.length > 0);
            if (isInternalLink) {
              nextCoupletID = jNodeID.attr('id');
            } // else no leads found in div
          }
        } // else: local id NOT found or NOT valid for player
      } // END if isInternalLink
      // Following is NOT an else, isInternalLink may have been changed.
      nextCoupletID = (!isInternalLink) ? '' : nextCoupletID;
      // prepare resultlink (page or internal subkey) for player
      $(this)
        .attr('target', (isInternalLink ? '_self' : '_blank'))
        .addClass('linkbtn').removeAttr('style')
        .click(function () {return jkeyDecision(this, false); });
      // pass autostart marker on (note: this.hash = this.hash + 'jkey-autostart' is ok in FF, but IE8 behaves strangely. Using full href in IE8 seems ok!)
      this.href = this.href + (this.hash.length ? '' : '#') + 'jkey-autostart';
      // save in data
      $.data(this, 'coupletID', {curr: currCoupletID, next: nextCoupletID});
    },
    eachLeadon = function () { // this = a leadon link
      var jThis = $(this);
      if (jThis.parent().hasClass('leadon')) { // for leadon (but not leadontext) overwrite display text
        jThis.html($.resource('jKey_coupletContinue'));
      }
      jThis.addClass('linkbtn').removeAttr('style');
      // General problem: changing link onclick attribute works in FF, but is ignored by IE (known bug)
      // One general solution is to build complete new link and delete previous one.
      // Also working is use of jquery.click(), but watch referencing 'this'. Here 'this' is correct because of each().
      jThis.click(function () {return jkeyDecision(this, false); });
      // save in data
      $.data(this, 'coupletID', {curr: currCoupletID, next: this.hash.substring(1, this.hash.length)});
    },
    //prefixID = function () {return 'jK' + this.id; },
    suffixID = function () {
      // leave the mw-customcollapsible untouched
      // if (this.id.indexOf('mw-customcollapsible') === 0) {
      //   return this.id;
      // } else {
      //   return this.id + 'jK';
      // }
      return this.id + 'jK';
    };
  // Process all leads in couplet
  // Leads may be non-consecutive (general nested order = "1 2 2* 1* 3 3*" or nested subkeys = "1 alpha beta 1*->2 2->3 2* gamma epsilon 3 3*").
  // jquery [attribute^=value] Matches elements that have specified attribute, starting with value.
  // Trailing "_" after currCoupletID because non-consecutive IDs possible ("Lz_1_row", "Lz_1000_row"); Example: 3 leads within couplet = "Lz_1_row"/"Lz_1_2_row"/"Lz_1_3_row"
  // Notes: * Using nextAll().andSelf() would add first lead (jDecisionRow) at the end
  // * Using .prev().nextAll() fails for first couplet of horizontal style, which has no row before!
  jDecisionRow.parent().children('tr.dt-row[id^=' + currCoupletID.replace(/(:|\.)/g,'\\$1') + '_]').each(function () {
    var jClonedRow = $(this).clone(true, true),
      jNodeCell;
    // prefix id of decision row itself and all descendants
    //jClonedRow.find('[id]').andSelf().attr('id', prefixID);
    //check if andSelf neccessary
    //jClonedRow.find('[id]').andSelf().attr('id', suffixID);
    jClonedRow.find('[id]').attr('id', suffixID);
    /**
     * test try default jQuery.makeCollapsible Tue Aug 08 2017 14:25:15 GMT+0200 (CEST)
     * jClonedRow.find('.pseudolink')
     * // remove MediaWiki's collapsible click event
     * .unbind('click.mw-collapse')
     * .attr('class', function (index, attr) {
     * // check for MediaWiki class mw-customtoggle-myKey and add jK suffix
     *   return attr.replace(/(mw-customtoggle-[^ ]+)/, '$1jK');
     * });
     * // remove all MediaWiki's collapsible class added previously by $.fn.makeCollapsible()
     * // leave mw-collapsed untouched!
     * jClonedRow
     * .find('.mw-made-collapsible')
     * .removeClass('mw-made-collapsible');
     */
    // replace lead identifier with arrow (normal) or blank (horizontal side-by-side leads)
    jNodeCell = jClonedRow.find('td.dt-nodeid:first');
    // CHECK why coupletID must be added here in addition to data stored generally for each link
    // couplet ID has to be extracted and stored as data
    $.data(jNodeCell.get(0), 'couplet', {id : $.trim(jNodeCell.text())}); // needed for editor & multipleStep startup
    jNodeCell.html((jClonedRow.get(0).className.search(/dt-row-hor\w+/) === -1) ? '►' : ' '); //\u25ba is ►
    jClonedRow.find('td.leadalt').empty();
    // Transform all relevant leadout (= result pages) and leadon/leadontext (next couplet) links
    // (depending on row mode, multiple links may exist)
    jClonedRow.find('span.leadout a').each(eachLeadout);
    jClonedRow.find('span.leadon a, div.leadontext a, span.leadontext a').each(eachLeadon);
    // initialize $.fn.makeCollapsible() again
    jPlayerCouplet.append(jClonedRow.show());
    // jPlayerCouplet.find('.mw-collapsible').makeCollapsible();
    var cutsomToggleOptions = {
      toggleClasses: true,
      toggleText: {
        collapseText: $.resource('CollapseBox_captionCollapse'),
        expandText: $.resource('CollapseBox_captionExpand')
      },
      $customTogglers : jPlayerCouplet.find('.mw-customtoggle')
    };
    jPlayerCouplet.find('[id^="mw-customcollapsible-"]').makeCollapsible(cutsomToggleOptions);
  }); // END each lead row of couplet
}

/*
 * Description: Load couplet in player
 * jPlayerDiv: main div around player
 * coupletID: id of the couplet to be loaded
 * isCertain: preset for certainty checkbox (normally true, but may be false when restoring from history)
 */
function jkeyLoadCouplet(jPlayerDiv, coupletID, isUncertain) {
  var jPlayerCouplet = jPlayerDiv.find('table.dt-body');
  jPlayerCouplet.empty(); // flush
  // coupletID could be 'a.34', jquery needs 'a\.34'
  // $('#'... -> document scope, coupletID may be in other key on same page
  jkeyTransformCouplet(jPlayerDiv, $('#' + coupletID.replace(/\./g, '\\.')).closest('tr'), jPlayerCouplet);
  // after history actions: results div may have to be hidden, and certainty div re-displayed
  jPlayerDiv.find('div.jkeyResultMsg').hide();
  jPlayerDiv.find('div.certaintyDiv').show()
    .find('input#decisionUncertain').get(0).checked = isUncertain; // setting checkbox
}

/*
 * Description: toggle visibility of player controls (top right player area)
 * mode: string for current control mode;
 *  values are 'resumed', 'overview', 'newstart', 'finished'
 * elementInKeyDiv: any element inside div.decisiontree or div.decisiontree itself, Either as DOM ref OR as '#id' string
 * (.closest() works inclusive!), usually a caller of a control (link, button).
 * returns: jKeyDiv to be further used elsewhere
 */
function jkeySetMode(mode, elementInKeyDiv) {
  try {
    // Cancel if called with undefined element; may occur for "newstart"
    // when called based on undefined id from location.hash
    if (elementInKeyDiv === undefined) {return; }
    var jKeyDiv = $(elementInKeyDiv).closest('div.decisiontree'),
      jKeyTable = jKeyDiv.find('table.dt-body:last'),
      jPlayerDiv = jKeyDiv.find('div.jkeyPlayer'),
      jPlayerCouplet = jPlayerDiv.find('table.dt-body'),
      jResultDiv = jPlayerDiv.find('div.jkeyResultMsg'),
      jControls = jKeyDiv.find('.jkeyControls'),
      finished,
      overview,
      uniquePlayerID;
    if (mode === 'firststart') { // change permanently: first-start to restart icon/text, add overview/resume controls, removes  toggleAllExtras!
      jControls.find('.jkeyPlayerStartNew').show();
      jControls.find('.jkeyPlayerStart1st, .jkeyToggleAllExtras').hide();
      window.scrollTo(0, jKeyDiv[0].offsetTop); // scroll to current key
      mode = 'newstart';
    }
    finished = (mode === 'finished');
    overview = !(mode === 'resumed' || mode === 'newstart' || finished);
    jControls.find('.jkeyPlayerOverview').toggle(!overview);
    jControls.find('.jkeyPlayerResume').toggle(overview);
    jKeyDiv.find('.dt-header,.dt-header-toggle').toggle(overview); // metadata box (description, audience, etc.) and right-floating "more" to show metadata box
    // Show finished message only in finished mode (+ set below for resumed)
    jResultDiv.toggle(finished);
    // set entire wiki content area to text color css3 rgba with transparency 0.2 (jkeyCanvas is already 1.0).
    // all separately colored text (links etc.) must be handled separately (add all, then remove inside player)
    // NOTE: css opacity not an option, opacity of children can not increase (= made more visible) !
    $('#bodyContent')
     .addClass('jkdimmed')
     .find('a,a.new,a:visited,a:hover,.pseudolink,.linbtn,h2,h3,h4,h5,h6,.commonnames').addClass('jkdimmed');
    jKeyDiv.find('.jkdimmed').removeClass('jkdimmed');
    switch (mode) {
    case ('overview'): // STOP player, hide player, show original overview player, undo low opacity content area
      jKeyDiv.removeClass('jkeyCanvas');
      jKeyTable.show();
      jPlayerDiv.hide();
      $('#bodyContent')
       .removeClass('jkdimmed')
       .find('.jkdimmed').removeClass('jkdimmed');
      break;
    case ('newstart'):
      // Delete player-div in case of restart (also invalidates jPlayerCouplet)
      jPlayerDiv.remove();
      jKeyTable.hide();
      // create new player & table, transform couplet
      jPlayerCouplet = $('<table class="dt-body" cellspacing="0" cellpadding="0"/>');
      jPlayerDiv = $('<div class="jkeyPlayer"/>')
        .append(jPlayerCouplet)
        // Append a hidden result section (for finished mode)
        .append($('<div class="jkeyResultMsg"/>').hide());
      if (!jKeyDiv.hasClass("jkey-simplified")) {
        jPlayerDiv.append(
          '<div class="certaintyDiv noprint">'
            + '<input type="checkbox" id="decisionUncertain" value="1" /> '
            + '<label for="decisionUncertain" style="font-weight:bold" title="'+$.resource("jKey_certaintyTooltip")+'">'
            + $.resource('jKey_certaintyLabel') + '</label>&nbsp;'
            + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '
            + $.linkBuilder('jKey_tryAllAlternatives','','#',' class="small-linkbtn" onclick="return jkeyTryAll(this);" title="'+$.resource('jKey_tryAllAlternativesTooltip')+"'")
            + '</div>'
        );
      }
      // generate unique playerID (multiple keys may exist on 1 page!):
      do {
        uniquePlayerID = 'jkp_' + $.random(1, 9999999);
      } while (jkeyHistory.uniquePlayerID); // until ID not yet used
      // add id to connect with history
      jPlayerDiv.attr('id', uniquePlayerID);
      // initalize player history class (lazy loading)
      jkeyHistory[uniquePlayerID] = new PlayerHistory(jPlayerDiv);
      // Initialize player with first decision row (table may start with spacer row)
      jKeyTable.before(jPlayerDiv); // add to DOM
      jkeyTransformCouplet(jKeyDiv, jKeyTable.find('tr.dt-row:first'), jPlayerCouplet);
      // NOTE: NO BREAK here, fallthrough to 'resumed' intended!
    case ('resumed'): // = player resumed. This may have to show couplet or finished mode
      // style change for key div + hide original table & show table in player
      jKeyDiv.addClass('jkeyCanvas');
      jKeyTable.hide();
      jPlayerCouplet.show();
      jPlayerDiv.show();
      // redisplay result div if still filled
      jResultDiv.toggle(jResultDiv.text().length > 0);
      break;
    case ('finished'): // empty current couplet, hide certainty checkbox
      jPlayerCouplet.empty();
      jPlayerDiv.find('div.certaintyDiv').hide();
      break;
    } // end case
    return false; // cancel default event
  } catch (err) {
    jExceptionAlert(err);
  }
}

/*
 * Description: Load result in  player
 * jPlayerDiv: main div around player
 * jResult = $ object containing the combined result html (commonnames, resultlink, qualifier); will be cloned
 */
function jkeyLoadResult(jPlayerDiv, jResult) {
  jPlayerDiv.find('div.jkeyResultMsg').empty().html($.resource('jKey_mainResultMsg')).append(jResult.clone());
  jkeySetMode('finished', jPlayerDiv);
}

/*
 * Description: perform 'confirm' or 'revise' action from history
 * caller: DOM link; confirm if class=histStepActionConfirm, revise if histStepActionRevise
 * revCoupletID, confCoupletID: id of couplet to be revised or confirmed
 */
function jkeyHistoryAction(caller, revCoupletID, confCoupletID) {
  try {
    // get currently active history (find first & look in hierarchy)
    var jCaller = $(caller),
      jStepItem = jCaller.closest('tr'), // history row around action link
      jPlayerDiv = jStepItem.closest('div.jkeyPlayer'),
      jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
      firstConfirmableStep,
      nextDecisionIsUncertain;
    // set history block to the one containing the caller (or outermost histTable itself)
    jCurrHistory.changeActiveBlock(jCaller.closest('table.histBlock, table.histTable'));
    // Handle possible history change
    if (jCaller.hasClass('histStepActionConfirm')) { // confirm was clicked
      revCoupletID = confCoupletID; // revCoupletID = next couplet to load
      // Advance to next history step
      jStepItem = jStepItem.nextAll('tr.histStep:first');
      // Update history step certainty from player checkbox
      firstConfirmableStep = jCurrHistory.getfirstConfirmableStep();
      if (firstConfirmableStep) { // null if none found
        // firstConfirmableStep is the couplet shown in player!
        jCurrHistory.withHistoryStep(firstConfirmableStep)
          .setUncertainty(jPlayerDiv.find('div.certaintyDiv input#decisionUncertain').is(':checked'));
      }
    }
    // Remove confirm 'confirmable-decisions' sub-heading; will be recreated in updateConfirmability if necessary
    jCurrHistory.getCurrBlock().find('tr.histConfirmSubhdg').remove();
    if (revCoupletID === '') { // RESULT
      // try-all may result in multiple alternative results. Thus confirming a result needs to refresh rather than re-display
      jkeyLoadResult(jPlayerDiv, jCaller.closest('tr.histStep').next('tr.histResult').find('span.leadout'));
    } else { // Refresh player with couplet corresponding to jStep
      nextDecisionIsUncertain = false; // default
      if (jStepItem.length) { // extract history step certainty
        nextDecisionIsUncertain = jCurrHistory.withHistoryStep(jStepItem.get(0)).getUncertainty();
      }
      jkeyLoadCouplet(jPlayerDiv, revCoupletID, nextDecisionIsUncertain);
    }
    jCurrHistory.updateConfirmability(jStepItem.get(0));
  } catch (err) {
    jExceptionAlert(err);
  }
  return false; // cancel default event
}

/*
 * Description: Get and simplify corresponding leadtxt (without links etc., used for history items)
 * Notes: Templates Lead and Decision Horizontal use class:leadon and caller-link (class leadon) is in td.leadresult
 * > (Decision Horizontal uses both leadresult and leadresult-hor1 as class names)
 * > Template Lead Link (cross-refs) uses class:leadontext,
 * > Decision S2 uses class:leadspan; here caller is in th.leadtxt!
 * leadLinkCaller: DOM reference of the calling element, which has to be a link (e.g: under leadon span)
 */
function jkeySimplifiedLeadtxt(leadLinkCaller) {
  var jContainer = $(leadLinkCaller).closest('td.leadresult, th.leadtxt, td.leadtxt'), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
    jSpan,
    jSimplifiedLeadTxt;
  if (jContainer.hasClass('leadtxt')) {
    // occurs if leadspan itself is formatted as link (having both leadspan and leadontext class)
    jSpan = jContainer.find('span.leadspan');
  } else if (jContainer.hasClass('leadresult')) {
    // pos of span.leadspan depends on arrangement (horizontal or not)
    if (jContainer.get(0).className.search(/leadresult-hor/) !== -1) {
      // result is separated by a table line.
      jSpan = jContainer.closest('table').find('td.leadtxt:nth-child(' + (jContainer.get(0).cellIndex + 1) + ')').find('span.leadspan');
    } else {
      jSpan = jContainer.closest('table').find('td.leadtxt').find('span.leadspan');
    }
  }
  if (jSpan.length === 0) {
    throw new jException('NotFound', {
      info: 'No lead statement!',
      jContainer: jContainer,
      jContainer_closest_table: jContainer.closest('table'),
      leadLinkCaller: leadLinkCaller
    });
  }
  // Clone span; remove links, breaks, imgs
  jSimplifiedLeadTxt = jSpan.clone(true);
  jSimplifiedLeadTxt.find('a').each(function () {$(this).replaceWith($(this).html()); });
  jSimplifiedLeadTxt.find('br, img').remove();
  return jSimplifiedLeadTxt;
}

/**
 * @description: Used in onclick events for both next couplet and final result, i.e. user made a decision.
 *  Add current lead to history, show next couplet in player or finish (result)
 * @param {selector} caller DOM reference of the calling element (has $.data with members: next & curr (couplet ID))
 * @param {boolean} quiet do not output couplets or results, only add to history
 */
function jkeyDecision(caller, quiet) {
  try {
    var jCaller = $(caller),
      jContainer = jCaller.closest('td.leadresult, th.leadtxt, td.leadtxt'), // TODO remove th.leadtxt later (Freitag, 23. März 2012 13:42)
      jPlayerDiv = jContainer.closest('div.jkeyPlayer'),
      jSimplifiedLeadTxt = jkeySimplifiedLeadtxt(caller), // single time this is called
      jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
      nextCoupletID = $.data(caller, 'coupletID').next, // ref to id attribute of next couplet when used on next couplet link
      firstConfirmableStep,
      jParentBlock,
      jStep,
      firstConfItemActionLink,
      jResult;
    // Decision in player may be identical to current confirmable history step
    firstConfirmableStep = jCurrHistory.getfirstConfirmableStep();
    if (firstConfirmableStep) { // = null if not found
      // is it a nested block and is it first step, which has to be confirmed?
      // -> user decided to change path so that the nested block will be removed
      jParentBlock = jCurrHistory.getParentBlock(); // null if not nested
      // in inner, nested blocks, confirming first step implies removing the try-all structure
      if (!jParentBlock.is('.histTable') && jCurrHistory.isFirstStepInBlockConfirmableStep()) {
        jCurrHistory.setCurrBlock(jParentBlock);
        jCurrHistory.setCurrBlockActive(true);
        jCurrHistory.cleanupNestedBlocks();
      } else { //
        jStep = $(firstConfirmableStep);
        // Is nextCoupletID identical to hash of firstConfirmableStep action link?
        firstConfItemActionLink = jStep.find('td.histStepActions a').get(0);
        if (firstConfItemActionLink.hash === '#' + nextCoupletID) {
          // Selection in player is identical to current confirmable history action, i.e. does NOT change path.
          // Execute confirm and exit jkeyDecision immediately after
          jkeyHistoryAction(firstConfItemActionLink, nextCoupletID, nextCoupletID);
          return false;
        }
        // from here, path has changed; rework history. 1. Remove confirm sub-heading, item itself & following siblings
        jCurrHistory.cleanupAfter(jStep);
        // empty result-div of player (no longer valid)
        jPlayerDiv.children('div.jkeyResultMsg').empty().hide();
      }
    } else { // enable history div (disabled at start)
      jPlayerDiv.find('div.jkeyHistory').show();
      // remove all following if path has changed
      jCurrHistory.cleanupNestedBlocks();
    }
    // Add new step with simplified lead text to history
    jCurrHistory.createHistoryStep(
      jSimplifiedLeadTxt.html(),
      jPlayerDiv.find('div.certaintyDiv input#decisionUncertain').is(':checked'),
      nextCoupletID,
      $.data(caller, 'coupletID').curr
    );
    if (nextCoupletID.length) { // -> NEXT couplet
      if (!quiet) {
        // Last parameter false: default (i.e. user can change this later) for NEXT decision is certain
        jkeyLoadCouplet(jPlayerDiv, nextCoupletID, false);
      }
    } else { // -> RESULT
      // Prepare main result link, common names and resultqualifier (latter 2 may be missing!).
      jResult = $('<span class="leadout"/>')
        .append(jContainer.find('span.commonnames').clone().append(' '))
        .append(jCaller.clone().removeClass('linkbtn').unbind('click'))
        .append(jContainer.find('span.resultqualifier').clone().prepend(' '));
      jResult.find('br, img').remove(); // don't combine with above!
      // Add History result row (use .clone(), result already used above)
      // ## TODO: is it possible to avoid redundant span element? Text node?
      jCurrHistory.createHistoryResult($('<span/>').append($.resource('jKey_historyResult')).append(jResult));
      if (!quiet) { // load into main result area
        jkeyLoadResult(jPlayerDiv, jResult);
      }
    }
  } catch (err) {
    jExceptionAlert(err);
  }
  return false; // cancel default event
}

/**
 * @description: Used in onclick event of button "try all decisions"
 * 
 * @requires $.resource()
 * @param {selector} caller DOM reference to a link
 * @returns {Boolean} False
 */
function jkeyTryAll(caller) {
  var eachDecisionLink = function (jCurrHistory, jParentBlock, caller, idx) {
      // create new block for current link & add it to parent block. +idx = unary operator to cast to numeric
      var jBlock = jCurrHistory.createBlock(false, false, $.resource('jKey_historyNested') + ' ' + (+idx + 1) + ':');
      jCurrHistory.createHistoryNested(jBlock);
      jCurrHistory.setCurrBlock(jBlock);
      jkeyDecision(caller, true); // true = no couplet/result loading into main window, history only
      jCurrHistory.setCurrBlock(jParentBlock); // revert current block to parent
    },
    jPlayerDiv = $(caller).closest('div.jkeyPlayer'),
    jCurrHistory = jkeyHistory[jPlayerDiv.attr('id')],
    jNestingParent;
  // Only if nested steps not already present:
  if (jCurrHistory.firstNestedStep().length === 0) {
    // jkeyTryAll always implies that all later steps must be removed
    jCurrHistory.cleanupConfirmableSteps();
    jNestingParent = jCurrHistory.getCurrBlock(); // preserve current for loop
    // add all lead-on and lead-out links to history
    jPlayerDiv.find('table.dt-body:first').find('span.leadon a, span.leadout a').each(function (idx) {
      eachDecisionLink(jCurrHistory, jNestingParent, this, idx);
    });
  }
  // activate first Nested history block; mark as active in history, then load couplet or result
  jCurrHistory.firstNestedStep().find('span.histHeaderActive a').click();
  return false; // cancel default event
}
/**
 * @description: switch between history blocks (multiple alternatives if couplet could not be decided)
 * 
 * @param {type} caller DOM reference to a link
 * @returns {Boolean}
 */
function jkeySwitchHistory(caller) {
  var jClosestHistory = $(caller).closest('table.histBlock, table.histTable');
  // set new & show last entry
  jkeyHistory[jClosestHistory.closest('div.jkeyPlayer').attr('id')].changeActiveBlock(jClosestHistory);
  // find directly last step (excluding nested), 2x click is: revise, then confirm
  jClosestHistory.find('tr:first').nextAll('tr.histStep:last').find('td.histStepActions a').click().click();
  return false; // cancel default event
}

/**
 * @description Initialize interactive mode (step-by-step) for key if keys exist, init key editor delayed;
 * Also: start player automatically if URL has hash pointing into a valid key.
 * Adds event jkey:initialized
 * 
 * @requires $.imglinkBuilder()
 * @requires $.resource()
 * @returns {undefined}
 */
function jkeyInit() {
  var jKeys = $('div.decisiontree'),
    jKeysWithCtrls,
    fragmentID,
    jKeyAutostartPos,
    isKeyRef;
  // append jKey-specific resources to global jI18n, "true" = deep extension
  $.extend(true, $.jI18n, {
    en: {
      jKey_historyActive : 'http://upload.wikimedia.org/wikipedia/commons/thumb/9/94/Symbol_support_vote.svg/20px-Symbol_support_vote.svg.png',
      jKey_historyInactive  : 'http://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Symbol_partial_support_vote.svg/20px-Symbol_partial_support_vote.svg.png',
      jKey_historyActiveTooltip : 'This is the currently active history; decisions made above will append to this',
      jKey_historyInactiveTooltip  : 'Click here to make this alternative the active history',
      jKey_playerStart1st  : 'Step-by-step identification',
      jKey_playerStartNew  : 'Start new identification',
      jKey_playerOverview  : 'Key overview (printable)',
      jKey_playerResume  : 'Resume',
      jKey_coupletContinue : '&nbsp;Continue&nbsp;',
      jKey_editorEdit  : 'Edit Key',// unused
      jKey_editorSave  : 'Save',// unused
      jKey_certaintyLabel  : 'Flag this decision as uncertain',
      jKey_certaintyTooltip : 'If checkbox is activated, clicking on Next or a result (above) will flag the decision in the list of previous decisions with the word ‘uncertain’.',
      jKey_tryAllAlternatives  : 'Undecided: Try all alternatives',
      jKey_tryAllAlternativesTooltip : 'If not even an uncertain decision is possible, all alternatives may be followed in parallel. After finishing the first alternative, activate the other alternatives in the history of previous decisions (below).',
      jKey_mainResultMsg : 'You identified: ',
      jKey_historyHeading  : 'Previous decisions',
      jKey_historyConfirmable  : 'Confirmable decisions:',
      jKey_historyNested : 'Alternative',
      jKey_historyResult : 'Result: ',
      jKey_historyConfirm  : 'confirm',
      jKey_historyRevise : 'revise',
      jKey_historyUncertainFlag  : '(uncertain)',
      jKey_toolTipIsActivePath : 'Currently active identification path (multiple alternatives are being followed)' // unused
    },
    fr: {
      jKey_playerStart1st  : 'détermination interactive',
      jKey_playerStartNew  : 'détermination nouvelle',
      jKey_playerOverview  : 'Vue d’ensemble (imprimable)',
      jKey_playerResume  : 'continuer la détermination',
      jKey_coupletContinue : '&nbsp;avancer&nbsp;',
      jKey_editorEdit  : 'éditer',
      jKey_editorSave  : 'enregistrer',
      jKey_certaintyLabel  : 'Cette décision a marqué comme incertain',
      jKey_certaintyTooltip : 'Si la case à cocher est activée, cliquer sur « avancer » ou un résultat (ci-dessus) marquera la décision dans la liste des décisions précédentes avec le mot « incertain ».',
      jKey_tryAllAlternatives  : 'Indécidable: Suivez toutes les alternatives',
      jKey_tryAllAlternativesTooltip : 'Si même une décision incertaine est possible, toutes les alternatives peuvent être suivies en parallèle. Après avoir terminé la première alternative, activez les autres alternatives dans l’historique des décisions précédentes (ci-dessous).',
      jKey_mainResultMsg : 'résultat: ',
      jKey_historyActiveTooltip : 'Alternatif actif. Les décisions prises ci-dessus sont enregistrés ici',
      jKey_historyInactiveTooltip  : 'Cliquez ici pour poursuivre cette alternative',
      jKey_historyHeading  : 'Décisions antérieures',
      jKey_historyConfirmable  : 'Les décisions en cours d’examen:',
      jKey_historyResult : 'résultat: ',
      jKey_historyConfirm  : 'confirmer',
      jKey_historyRevise : 'vérifier',
      jKey_historyUncertainFlag  : '(incertain)',
      jKey_toolTipIsActivePath : 'Actuellement la route de détermination actif (plusieurs alternatives sont poursuivies)'
    },
    de: {
      jKey_playerStart1st  : 'Interaktive Bestimmung',
      jKey_playerStartNew  : 'Neue Bestimmung',
      jKey_playerOverview  : 'Übersicht (druckbar)',
      jKey_playerResume  : 'Bestimmung fortsetzen',
      jKey_coupletContinue : '&nbsp;Weiter&nbsp;',
      jKey_editorEdit  : 'Bearbeiten',
      jKey_editorSave  : 'Speichern',
      jKey_certaintyLabel  : 'Diese Entscheidung als unsicher kennzeichnen',
      jKey_certaintyTooltip : 'Wenn die Checkbox aktiviert ist, wird beim Klick auf ‚Weiter‘ oder ein Ergebnis diese Entscheidung in der Liste bisheriger Entscheidungen mit dem Wort ‚unsicher‘ gekennzeichnet.',
      jKey_tryAllAlternatives  : 'Nicht entscheidbar: Verfolge alle Alternativen',
      jKey_tryAllAlternativesTooltip : 'Wenn überhaupt keine Entscheidung möglich ist, können die Alternativen parallel verfolgt werden. Nachdem die erste Alternative zu Ende geführt wurde, können die Übrigen in der Liste der bisherigen Entscheidungen (unten) aktiviert werden.',
      jKey_mainResultMsg : 'Ergebnis: ',
      jKey_historyActiveTooltip : 'Aktive Alternative. Die oben getroffenen Entscheidungen werden hier aufgezeichnet',
      jKey_historyInactiveTooltip  : 'Klicken Sie hier, um diese Alternative weiter zu verfolgen',
      jKey_historyHeading  : 'Bisherige Entscheidungen',
      jKey_historyConfirmable  : 'Entscheidungen in Überprüfung:',
      jKey_historyResult : 'Ergebnis: ',
      jKey_historyConfirm  : 'bestätigen',
      jKey_historyRevise : 'überprüfen',
      jKey_historyUncertainFlag  : '(unsicher)',
      jKey_toolTipIsActivePath : 'Derzeit aktiver Bestimmungsweg (mehrere Alternativen werden verfolgt)'
    },
    it: {
      jKey_playerStart1st  : 'Esegui passo-dopo-passo',
      jKey_playerStartNew  : 'Nuova identificazione',
      jKey_playerOverview  : 'Sintesi completa (stampabile)',
      jKey_playerResume  : 'Ricomincia l’identificazione',
      jKey_coupletContinue : '&nbsp;Continua&nbsp;',
      jKey_editorEdit  : 'Modifica',
      jKey_editorSave  : 'Salva',
      jKey_certaintyLabel  : 'Segna scelta come insicura', //REVISE
      jKey_certaintyTooltip : '(click, then make your next decision)', //TRANSLATE
      jKey_mainResultMsg : 'Il risultato dell’identificazione è: ',
      jKey_historyHeading  : 'Scelte precedenti',
      jKey_historyConfirmable  : 'Scelta confermabile:',
      jKey_historyResult : 'Risultato: ',
      jKey_historyConfirm  : 'conferma',
      jKey_historyRevise : 'correggi',
      jKey_historyUncertainFlag  : '(incerta)',
      jKey_toolTipIsActivePath : 'Percorso di identificazione attualmente attivo (vengono seguite alternative multiple)'
    }
  });
  if (jKeys.length) { // only if at least one key exists
    $('head').append(
      '<style type="text/css">'
        // Canvas: top padding needed for IE, FF/Chrome could do without
        + 'div.jkeyCanvas {background-color:#FFFFFF; border:5px solid #FFC51A; padding:0.7em 1em 1em 1em; color:rgba(0,0,0,1.0);}\n'
        // do dim background around the jkeyCanvas, applied to body, links, etc.
        + '.jkdimmed {color:rgba(0,0,0,0.2) !important;}\n'
        // Safari 4 has problems with 100% table width:
        + ( ( navigator.userAgent.toUpperCase().indexOf('SAFARI') > 0 && parseFloat(navigator.appVersion) < 5 ) ? 'table.dt-caption {width:98%}\n' : '')
        + 'div.jkeyControls a {vertical-align: middle;}\n'
        + 'div.jkeyPlayer table.dt-body {margin:0.5em}\n'
        + 'div.jkeyResultMsg {clear:right; margin:0 0 1em; padding:1em; font-weight:bold; background-color:#EEF0F0; border:1px solid #444444; }\n'
        + 'div.jkeyResultMsg span.leadout, div.jkeyResultMsg span.commonnames {background-color:transparent}\n'
        + 'div.certaintyDiv {margin:1.8em 0 1em 0; padding:0.8em 0.2em; font-size:83%; color:#555;}\n'
        + 'input[type=checkbox] {vertical-align:middle;}\n' /* MAKE GENERIC?? */
        + 'div.jkeyHistory {margin-top:1em;}\n'
        + 'table.histTable, table.histBlock {background-color:transparent;}\n'
        + 'table.histBlock {border-left:1px solid; width:100%;}\n'
        + 'td.histStepNumber, td.histNestedEmpty, td.histResultSymbol {width:1em; padding:0 0.6em;}\n'
        + 'td.histHeaderContent, td.histConfirmSubhdgContent {padding-left:0.6em; font-weight:bold;}\n'
        + 'td.histStepActions {text-align:center; width:50px; padding-left:1em; }\n'
        + 'td.histStepNumber, td.histStepActions, td.histResultSymbol {vertical-align:top;}\n'
        + 'span.histStepCertainty {background-color:#FFA07A;}\n'
        + 'div.jkeyPlayer table.dt-body td.dt-nodeid, div.jkeyPlayer table.dt-body th.leadtxt, div.jkeyPlayer table.dt-body td.leadtxt, div.jkeyPlayer table.dt-body td.leadresult {padding-top:1em;line-height:1.7em}\n'
        + '</style>'
    );
    // flag: hide top metadata display (geoscope, creator, etc.)
    jKeys.filter('.jkey-hidekeymetadata').find('span.collapseButton:first a').click();
    // flag: show interactive key controls only for keys without class 'jkey-nocontrols'
    jKeysWithCtrls = jKeys.not('.jkey-nocontrols');
    // add player control + always add 'show-all-extras' checkbox (toggleAllExtras) in Overview mode (later removed)
    jKeysWithCtrls
      .find('.dt-caption')
      .prepend('<div class="jkeyControls" style="float:right;text-align:right; background-color:transparent; font-weight:bold; margin-bottom:6px"><span class="jkeyPlayerStart1st nowrap linkbtn" style="padding:4px 6px;">'
        + $.imglinkBuilder('jKey_iconStart1st', 'jKey_playerStart1st', 'onclick="return jkeySetMode(\'firststart\',this);"')
        + '</span><span class="jkeyToggleAllExtras nowrap"><br/><input type="checkbox" id="toggleAllExtras" class="toggleAllExtras" value="1" onclick="$.toggleAllCollapsible(this.checked, this)" onkeyup="$.toggleAllCollapsible(this.checked, this)" />'
        + ' <label for="toggleAllExtras" style="font-weight:normal;font-size:83%">' + $.resource('jKey_expandAll')
        + '</label><br/></span><span class="jkeyPlayerOverview nowrap">' +
        $.imglinkBuilder('jKey_iconOverview', 'jKey_playerOverview', 'onclick="return jkeySetMode(\'overview\',this);"')
        + '<br/></span><span class="jkeyPlayerResume nowrap">'
        + $.imglinkBuilder('jKey_iconResume', 'jKey_playerResume', 'onclick="return jkeySetMode(\'resumed\',this);"')
        + '<br/></span><span class="jkeyPlayerStartNew nowrap">'
        + $.imglinkBuilder('jKey_iconStartNew', 'jKey_playerStartNew', 'onclick="return jkeySetMode(\'newstart\',this);"') +
        '</span></div>')
      .find('.jkeyPlayerOverview,.jkeyPlayerResume,.jkeyPlayerStartNew').hide(); // RETEST WHETHER DIRECT HIDE/DISPLAY:NONE NOW POSSIBLE! putting display:none causes layout problems when showing them later!
    // Evaluate URL-hash-based and div-class-based autostart options:
    fragmentID = document.location.hash;
    if (fragmentID.length > 1) { // is non-empty hash (>1 to ignore '#' itself):
      jKeyAutostartPos = fragmentID.indexOf('jkey-autostart');
      if (jKeyAutostartPos > -1) { // test for presence of 'jkey-autostart' inside hash
        fragmentID = fragmentID.substring(0, jKeyAutostartPos); // remove jkey-autostart
        if (fragmentID.length === 1) { // just the '#'
          isKeyRef = false;
        } else {
          isKeyRef = jkeyisKeyRef($(fragmentID));
        }
        if (isKeyRef) { // existing ID, start this key (multiple keys may exist)
          jkeySetMode('firststart', fragmentID);
        } else { // else start first key
          jkeySetMode('firststart', jKeys.get(0));
        }
      }
    } else { // no URL-based autostart -> check for flag: jkey-autostart -> automatically change to interactive mode
      jKeys.filter('.jkey-autostart').each(function () {
        jkeySetMode('firststart', '#' + this.id);
      });
    }
    jKeys.trigger('jkey:initialized');
  }
}// jkeyInit()

$(document).ready(function () { // Called after html + all js loaded
  jkeyInit();
});