Forum Discussion

JeffRobedee's avatar
JeffRobedee
Community Member
3 years ago

"Superuser" Feature

*When I tried to submit a feature request the page blanks as soon as I select storyline 360*

 

It would be great if we could get a superuser feature where I as the course creator can either enter a password, or key combo that allows me to navigate the course. 

 

For example, on several courses I have either the player controls or added buttons hidden until triggered. However, for whatever reason, some users will get stuck, despite working out all variables (same machine, same browser, same LMS, cookies cleared, etc.). In other words, there's no rhyme or reason that 1 user can complete the course no issue, and then another user has an unreproducible error that doesn't let them continue. 

 

I can, of course, build in redundancy, but there's no guarantee that works, no guarantee the user can't exploit it, and it takes a lot of time. Ideally, it would be nice if as the course creator, I could use a key combo, or otherwise take over the course, proceed to the next slide, and then reset the controls so the user can continue and complete the course without needed to restart.

  • I created a simple example where the user can enter the Komani Code (Up, Up, Down,Down, Left, Right, Left, Right, A, B, enter) to activate a trigger.

    I used a variable that would increment if a key was pressed, but only if the variable had progressed with in a range or was = to a number.  This keeps the user from hitting the same key over and over. 
    Granted this approach only cares if the sequence is eventually entered, it ignores anything else.  Odds of someone hitting shuch a combo is very rare.

    • JeffRobedee's avatar
      JeffRobedee
      Community Member

      Thank you for the very helpful example.

      For context, some courses sporadically have an error that does not allow the user to progress (i.e. a variable is met where a value >/=4 changes the state of the next button to normal, however, despite the variable being 4 or greater, the next button does not change). 

      In my case, users are required to complete x hours for certification purposes, so most slides have requirements that must be met to proceed without skipping in order to fulfill the time requirement. If a user is 30 minutes into an hour long class, and the aforementioned error occurs, then my only recourse is to have them start over from the beginning-- which is of course, not desirable. 

      In the Komani code example, I'd have to implement this on every slide in addition to the other triggers that are there-- which could work, but definitely becomes time consuming. 

      The scenario I envision is being able to remote into a user's PC, somehow either change the state of objects, or possibly show the course menu, but then return the course to usermode so the user can continue the course without restarting it, after resolving the error. 

      The issue I have is that while I can remote into a user's PC and observe the error, it is sporadic, and I've yet to be able to reproduce an error; but it happens often enough that it is frustrating. 

      • PhilMayor's avatar
        PhilMayor
        Super Hero

        This could be built on a slide master so would then cover the whole course. Not sure I would want this feature built-in. IMHO most errors are with the course/developer rather than the LMS, once open a course is typically sandboxes from the LMS apart from sending data. This would introduce a back door that could be exploited.

        Sent from my iPhone

    • LaurieRosenb278's avatar
      LaurieRosenb278
      Community Member

      Hi Thaddeus, just wanted to point out that it's Konami, not Komani. Love that you did this, by the way. It took me all the way back to playing Contra as a kid. 

  • While designers might have a use for this.  I don't recommend relying on this for end users. 

  • If you are encountering roadblocks it might be useful to have a spare scene/slide/layer where in you have every navigation variable listed.  that way you can access this to check that course is working correctly.  This is what i'd use the cheat code to access.

    This sounds like a trigger issue, making some users superuser's doesn't seem like it would help in this regard.

    • PhilMayor's avatar
      PhilMayor
      Super Hero

      Agree, there is some JavaScript out there to open a window with all the variables listed and their current values.

      Sent from my iPhone

  • Also if you have users who get stuck, I recommend you record their circumstances.  This can help narrow down the bug.

    • JeffRobedee's avatar
      JeffRobedee
      Community Member

      Yes, this has been done many times, and I've even submitted the files to Articulate for assistance. Their response is that it is an LMS issue, which I don't have control over.

  • This is the code I use

    //Opens a Storyline variable debugger window

    //Add this JavaScript to some Storyline trigger (ex: when user presses F9 in the slide master)

     

    "use strict";

    var pl = GetPlayer();

    var debugWin = window.open(undefined, "debug", "width=600,height=800,toolbar=no,location=no");

    var debugDoc = debugWin.document;

    debugDoc.body.innerHTML = "";

    debugDoc.title = "Storyline Debugger";

    var refreshButton = debugDoc.createElement("INPUT");

    refreshButton.value = "Refresh";

    refreshButton.type = "button";

    refreshButton.addEventListener("click", refreshVars);

    debugDoc.body.appendChild(refreshButton);

    var tableElem = debugDoc.createElement("TABLE");

    debugDoc.body.appendChild(tableElem);

    getStorylineUserVariables(function (slVars) {

     slVars.sort(function(x, y){return x.name > y.name;});

     for (var a = 0; a < slVars.length; a++) {

     var v = slVars[a];

     var trElem = debugDoc.createElement("TR");

     if (a % 2 == 1) trElem.style.backgroundColor = "#dddddd";

     var tdElem = debugDoc.createElement("TD");

     tdElem.innerHTML = v.name;

     tdElem.style.padding = "10px 2px";

     trElem.appendChild(tdElem);

     var inputElem = debugDoc.createElement("INPUT");

     inputElem.storylineVarName = v.name;

     if (v.type == "number") {

     inputElem.type = "number";

     inputElem.step = undefined;

     inputElem.value = pl.GetVar(v.name);

     } else if (v.type == "boolean") {

     inputElem.type = "checkbox";

     inputElem.checked = pl.GetVar(v.name);

     } else {

     inputElem.type = "text";

     inputElem.value = pl.GetVar(v.name);

     }

     tdElem = debugDoc.createElement("TD");

     tdElem.appendChild(inputElem);

     trElem.appendChild(tdElem);

     tableElem.appendChild(trElem);

     }

    } );

    tableElem.addEventListener("change", function (e) {

     var inputElem = e.target;

     var slVarName = inputElem.storylineVarName;

     var newVal = "";

     if (inputElem.getAttribute("type") == "checkbox") {

     newVal = inputElem.checked;

     } else {

     newVal = inputElem.value;

     }

     pl.SetVar(slVarName, newVal);

    } );

     

    function refreshVars () {

     var inputs = tableElem.querySelectorAll("input");

     for (var a = 0; a < inputs.length; a++) {

     var elem = inputs[a];

     var val = pl.GetVar(elem.storylineVarName);

     if (elem.type == "checkbox") {

     elem.checked = val;

     } else {

     elem.value = val;

     }

     }

    }

     

    function getStorylineUserVariables(callback) {

     //Overide Storyline's globalProvideData function temporarily

     var origGlobalProvideData = window.globalProvideData;

     var scriptElem = document.createElement("SCRIPT");

     scriptElem.src = "html5/data/js/data.js";

     window.globalProvideData = function (foo, data) {

     var dataObj = JSON.parse(data);

     var variableList = [];

     for (var a = 0; a < dataObj.variables.length; a++) {

     var varObj = {name: dataObj.variables[a].name, type: dataObj.variables[a].type, defaultValue: dataObj.variables[a].value};

     if (varObj.name.match(/^[0-9a-zA-Z]{11}_RetryModeInteractionIncompleteOnLoad$/) == null && 

     varObj.name.match(/^LastSlideViewed_[0-9a-zA-Z]{11}$/) == null && 

     varObj.name.match(/^PrintPromptUsername_[0-9a-zA-Z]{11}$/) == null &&

     varObj.name.match(/^PrintPromptWindowQuizVar_[0-9a-zA-Z]{11}$/) == null){

     

     variableList.push(varObj);

     }

     }

     document.body.removeChild(scriptElem);

     window.globalProvideData = origGlobalProvideData;

     callback(variableList);

     }

     document.body.appendChild(scriptElem);

    }