Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Twine 1.4.2 SugarCube 2.13 menu cursor problem

edited April 2017 in Help! with 1.x
Hi there, this is my first question!
I was looking for a final fantasy type menu and came upon this fiddle

http://jsfiddle.net/2ZqN6/5/

While it works very well, as is, on a web page, it performs oddly in Twine. At first, I couldn't get it to work at all, then while I was searching the forums for other things I came across a post by CoraBlue twinery.org/forum/discussion/1051/performing-a-macro-after-page-displays/p1 about the very SAME menu.

I found that it needs to be in the PassageDone passage and put this script there:
<<if passage() is "Start" or "menu_test">>


<<script>>
//SET UP VARIABLES//

var cells = document.getElementsByTagName("li");
var cursorposition = 0;
var maxposition = cells.length-1;
var ScrollKeysToDisable = new Array(35, 36, 37, 38, 39, 40, 8);

//LOAD MEDIA//

var beep = new Audio('http://rpg.hamsterrepublic.com/wiki-images/8/8e/Confirm8-Bit.ogg');
var select = new Audio('http://rpg.hamsterrepublic.com/wiki-images/2/21/Collision8-Bit.ogg');
cells[cursorposition].className = cells[cursorposition].className + " pointer";

//ON KEYPRESS//

$(document).on('keydown', function (e) {

    //GET ALL THESE VARIABLES EACH KEYPRESS//

    var key = e.which; //THE KEY THAT IS PRESSED IN KEYCODE//
    var char = String.fromCharCode(key); //THE KEY THAT IS PRESSED IN STRING FORM//

    //DOWN ARROW//

    if (e.keyCode == 40 && cursorposition < maxposition) {
        cursorposition += 1;
    }

    //UP ARROW//

    if (e.keyCode == 38 && cursorposition > 0) {
        cursorposition -= 1;
    }

    //LEFT ARROW//

    if (e.keyCode == 37) {

    }

    //RIGHT ARROW//

    if (e.keyCode == 39) {

    }

    //PLAY BEEP//

    if (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40) {
        beep.currentTime = 0;
        beep.play();
    }

    //ENTER//

    if (e.keyCode == 13) {

    }

    //CATCH ALL ALPHANUMERIC KEYS//

    if (e.shiftKey && e.keyCode > 47 && e.keyCode < 91) {

    }

    //CONVERT TO LOWERCASE IF SHIFT NOT HELD//

    if (!e.shiftKey && e.keyCode > 47 && e.keyCode < 91) {
        if (e.keyCode > 57) {
            var char = String.fromCharCode(key + 32);
        }

    }

    //SPACEBAR//

    if (e.keyCode == 32 && macroName == 'cursortable') {

    }

    //BACKSPACE//

    if (e.keyCode == 8 && macroName == 'cursortable') {

    }

    //PLAY SELECT//

    if (e.keyCode == 8 || e.keyCode == 32 || e.keyCode == 13 || e.keyCode > 47 && e.keyCode < 91) {
        select.currentTime = 0;
        select.play();
    }

    //PREVENT DISABLED KEYS//
    if ($.inArray(key, ScrollKeysToDisable) > -1) {
        e.preventDefault();
        e.stopPropagation();
    }

    //REFRESH CURSOR AFTER DONE//
    for(var i=0; i<cells.length; i++){ cells[i].className = "" }
    cells[cursorposition].className = cells[cursorposition].className + " pointer";
    
});

<</script>>

<<endif>>

My menu_test passage has:
@.choice;New Game@@.choice;Load Game@@.choice;Quit@@



<div class="big-box">
    <ul>
        <li>New Game</li>
        <li>Continue</li>
        <li>Quit</li>
    </ul>
</div>


</div>

My css is:
/* finger cursor menu  */

 .big-box {
    background-color:#ddd;
    width:300px;
    margin:auto;
    position:relative;
    padding:5px 35px;
}
.big-box ul {
    list-style:none inside none;
    padding:0;
    font-size:40px;
}
.pointer:before {
    content:url("http://static2.wikia.nocookie.net/__cb20121113193557/finalfantasy/images/1/1b/FF1_PSP_cursor.png";);
    position:absolute;
    left:0px;
}

What displays is the gray box with the words, but NO pointing finger. At first, I thought, I must have mis-typed something. After I tried using my arrow keys, the finger magically appeared, after I clicked the down arrow twice. When moving down through the menu, it works as it should, and stops at the last list item. When moving back up, again, it disappears, when I click the up arrow, when the finger is placed on the "New Game" li.

What I want is for the pointer to be focused on the first menu item after the page loads and if somebody clicks the up arrow too many times the pointer would not move past the top menu item.

I am pretty good with html and css. I can usually get php to do what I want. I have very little experience with JavaScript, but can usually figure out how to change variables to get it to do what I want. Reading the forums has helped a lot.

I played around with the css, thinking it might be the z-index or positioning, or the online cursor image, so I put my own pointing finger in the local folder, but nothing seemed to affect the appearing and disappearing of the finger image. The code I have, above, is exactly what was on the fiddle frame, minus the
<script type='text/javascript'>//<![CDATA[
$(window).on('load', function() {
, as I have found errors on fiddle codes previously.

I tried removing all the comments, but nothing changed. I did notice that some of the script is colored green and the rest is black. I take it that may have something to do with the problem. Sorry to be so long on this. I don't usually ask for help, so this is new for me. I wanted to give as much information as I have available. Please, be kind and constructive when critical.
Thanks!

Comments

  • edited April 2017
    I'm short on time and posting from my phone, however, the following is not how you write what you're attempting:
    <<if passage() is "Start" or "menu_test">>
    
    That should be something like the following:
    <<if passage() is "Start" or passage() is "menu_test">>
    


    I'll take a closer look at the rest when I can.
  • I'm short on time and posting from my phone, however, the following is not how you write what you're attempting:
    <<if passage() is "Start" or "menu_test">>
    
    That should be something like the following:
    <<if passage() is "Start" or passage() is "menu_test">>
    


    I'll take a closer look at the rest when I can.

    Thank you, very much!
    I had an inkling that was the case. I'll have to go through my other passages and check for more of the same. Thanks, again. Looking forward to finding a solution!

  • Yesterday, I downloaded and installed the new SugarCube 2.17. I decided to take my menu_test out of my game and test it in a new blank Twine. It worked! What was different?
    I have a tag on Start and menu_test, called noui.

    noui has the css:
    body.noui #ui-bar { display:none; }
    body.noui, body.noui #story { margin: 0 auto; }
    

    Without that tag, the finger is starting at the top of the ui-bar and two links later, it hits the menu test.

    Then, I noticed something else. The PassageDone passage is no longer green at the top, but is blue like a normal passage. So, I check out the docs for special names and see:
    Never combine special passages with special tags. By doing so, you will probably break things in subtle and hard to detect ways.

    I know I saw this before, but I thought it meant not to tag a special passage(which I did). I know I must sound dumb, but after looking at the list of special tags on the same page, I am confused. How exactly do you mean "combine"?



  • edited April 2017
    EarthMagic wrote: »
    How exactly do you mean "combine"?
    Basically, don't assign any of the tags found in the Tag Names list to any passage found in the Passage Names list.

    note: the one possible exception to the above rule is the nobr tag, which may work correctly when assigned to some of the special passages but not all.
  • Thank you, for that!
    So, does this mean, in general, using a class tag "zz" on a passage is not going to break a PassageDone inclusion?
    If so, then it appears my script is seeing the ui-bar list items, even though they have a "display: none" attribute in the css.
  • Without an example of your 'script' it is difficult to comment on what it is doing.

    warning: Using CSS like display:none; on a parent element (eg. #ui-bar) only causes that element (and its li element children) to not be visualised in the page's output. The elements still exist in the Document Object Model (DOM) so they can still be found when using Javascript like document.getElementsByTagName("li")
  • greyelf said:
    warning: Using CSS like display:none; on a parent element (eg. #ui-bar) only causes that element (and its li element children) to not be visualised in the page's output. The elements still exist in the Document Object Model (DOM) so they can still be found when using Javascript like document.getElementsByTagName("li")

    Thank you. I did not know that.

    "my script" is the same as the script in my PassageDone passage, at the top of this page, with the exception that I have changed the first line.
    <<if passage() is "Start" or passage() is "menu_test">>
    
    
    <<script>>
    //SET UP VARIABLES//
    
    var cells = document.getElementsByTagName("li");
    var cursorposition = 0;
    var maxposition = cells.length-1;
    var ScrollKeysToDisable = new Array(35, 36, 37, 38, 39, 40, 8);
    
    //LOAD MEDIA//
    
    var beep = new Audio('http://rpg.hamsterrepublic.com/wiki-images/8/8e/Confirm8-Bit.ogg');
    var select = new Audio('http://rpg.hamsterrepublic.com/wiki-images/2/21/Collision8-Bit.ogg');
    cells[cursorposition].className = cells[cursorposition].className + " pointer";
    
    //ON KEYPRESS//
    
    $(document).on('keydown', function (e) {
    
        //GET ALL THESE VARIABLES EACH KEYPRESS//
    
        var key = e.which; //THE KEY THAT IS PRESSED IN KEYCODE//
        var char = String.fromCharCode(key); //THE KEY THAT IS PRESSED IN STRING FORM//
    
        //DOWN ARROW//
    
        if (e.keyCode == 40 && cursorposition < maxposition) {
            cursorposition += 1;
        }
    
        //UP ARROW//
    
        if (e.keyCode == 38 && cursorposition > 0) {
            cursorposition -= 1;
        }
    
        //LEFT ARROW//
    
        if (e.keyCode == 37) {
    
        }
    
        //RIGHT ARROW//
    
        if (e.keyCode == 39) {
    
        }
    
        //PLAY BEEP//
    
        if (e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40) {
            beep.currentTime = 0;
            beep.play();
        }
    
        //ENTER//
    
        if (e.keyCode == 13) {
    
        }
    
        //CATCH ALL ALPHANUMERIC KEYS//
    
        if (e.shiftKey && e.keyCode > 47 && e.keyCode < 91) {
    
        }
    
        //CONVERT TO LOWERCASE IF SHIFT NOT HELD//
    
        if (!e.shiftKey && e.keyCode > 47 && e.keyCode < 91) {
            if (e.keyCode > 57) {
                var char = String.fromCharCode(key + 32);
            }
    
        }
    
        //SPACEBAR//
    
        if (e.keyCode == 32 && macroName == 'cursortable') {
    
        }
    
        //BACKSPACE//
    
        if (e.keyCode == 8 && macroName == 'cursortable') {
    
        }
    
        //PLAY SELECT//
    
        if (e.keyCode == 8 || e.keyCode == 32 || e.keyCode == 13 || e.keyCode > 47 && e.keyCode < 91) {
            select.currentTime = 0;
            select.play();
        }
    
        //PREVENT DISABLED KEYS//
        if ($.inArray(key, ScrollKeysToDisable) > -1) {
            e.preventDefault();
            e.stopPropagation();
        }
    
        //REFRESH CURSOR AFTER DONE//
        for(var i=0; i<cells.length; i++){ cells[i].className = "" }
        cells[cursorposition].className = cells[cursorposition].className + " pointer";
        
    });
    
    <</script>>
    
    <<endif>>
    

    I tried changing the var that looks for the list item
    var cells = document.getElementsByTagName("li");
    
    to
    var cells = document.getElementsByClassName("mymenu");
    

    but it breaks the script, something about the
        for(var i=0; i<cells.length; i++){ cells[i].className = "" }
        cells[cursorposition].className = cells[cursorposition].className + " pointer";
    
    I know very little about JavaScript(but I am learning). It can't be that hard to have it look for list items with an existing class name, can it?

    Also, I just want to say that I truly appreciate the fact that you(all) take time to help others learn how to do things with Twine and JavaScript. I would have quit the first week, if it hadn't been for these forums and all the knowledge that is shared here.
  • I am going to assume you change the main passage content to something like the following when you changed your PassageDone to use getElementsByClassName:
    @.choice;New Game@@.choice;Load Game@@.choice;Quit@@
    
    <div class="big-box">
        <ul>
            <li class="mymenu">New Game</li>
            <li class="mymenu">Continue</li>
            <li class="mymenu">Quit</li>
        </ul>
    </div>
    
    </div>
    


    The problem is quite simple, at the end of the code that handles each key-press you empty the current class property values of your li elements.
    for(var i=0; i<cells.length; i++){ cells[i].className = "" }
    
    This means that after the first key-press none of those elements has a class of mymenu, this in turn means the cells variable will contain an empty array which will result in an error when you try to access the cursorposition element of the array.
  • edited April 2017
    Your primary issue is that you're selecting all list item nodes on the page, rather than only the ones belonging to your menu. Beyond that, you have several other issues with your code.

    I'd suggest using something like the following as a starting point. As-is, pressing enter/return or spacebar will activate the menu links—in addition to clicking on them, naturally.


    JavaScript (Twine 1: goes in script-tagged passage; Twine 2: goes in Story JavaScript)
    /*
    	rpgMenu - A Menu reminiscent of those found in classic console RPGs.
    */
    (function () {
    	/*
    		Set up the audio media and utility functions.
    	*/
    	var beep   = SimpleAudio.create(['http://rpg.hamsterrepublic.com/wiki-images/8/8e/Confirm8-Bit.ogg']);
    	var select = SimpleAudio.create(['http://rpg.hamsterrepublic.com/wiki-images/2/21/Collision8-Bit.ogg']);
    
    	function playBeep() {
    		beep.stop();
    		beep.play();
    	}
    
    	function playSelect() {
    		select.stop();
    		select.play();
    	}
    
    	/*
    		Set up `setup.rpgMenu()` static method.
    	*/
    	setup.rpgMenu = function () {
    		var menuItems    = document.querySelectorAll('#rpgmenu li');
    		var menuIndex    = 0;
    		var disabledKeys = [8, 35, 36, 37, 38, 39, 40];
    
    		// Set up utility functions to update the focused element and cursor.
    		function focusLink() {
    			var link = menuItems[menuIndex].querySelector('a');
    			if (link) {
    				link.focus();
    			}
    		}
    
    		function menuUp() {
    			if (menuIndex > 0) {
    				menuItems[menuIndex].classList.remove('pointer');
    				--menuIndex;
    				menuItems[menuIndex].classList.add('pointer');
    				focusLink();
    			}
    		}
    
    		function menuDown() {
    			if (menuIndex < menuItems.length - 1) {
    				menuItems[menuIndex].classList.remove('pointer');
    				++menuIndex;
    				menuItems[menuIndex].classList.add('pointer');
    				focusLink();
    			}
    		}
    
    		// Set the initial element focus and cursor position.
    		menuItems[menuIndex].classList.add('pointer');
    		setTimeout(focusLink, 100); // timeout required here due to browser shenanigans
    
    		// Set up a `keydown` event handler.
    		$(document).on('keydown', function (ev) {
    			var key = ev.which;
    
    			// Stop event bubbling and the default action of select keys.
    			if (disabledKeys.includes(key)) {
    				ev.stopPropagation();
    				ev.preventDefault();
    			}
    
    			// Backspace.
    			if (key === 8) {
    				/* … */
    				playSelect();
    			}
    
    			// Enter.
    			else if (key === 13) {
    				/* … */
    				playSelect();
    			}
    
    			// Spacebar.
    			else if (key === 32) {
    				/* … */
    				playSelect();
    			}
    
    			// Left arrow.
    			else if (key === 37) {
    				/* … */
    				playBeep();
    			}
    
    			// Up arrow.
    			else if (key === 38) {
    				menuUp();
    				playBeep();
    			}
    
    			// Right arrow.
    			else if (key === 39) {
    				/* … */
    				playBeep();
    			}
    
    			// Down arrow.
    			else if (key === 40) {
    				menuDown();
    				playBeep();
    			}
    
    			// Top row numerals (0–9).
    			else if (key >= 48 && key <= 57) {
    				/* … */
    				playSelect();
    			}
    
    			// Keypad numerals (0–9).
    			else if (key >= 96 && key <= 105) {
    				/* … */
    				playSelect();
    			}
    
    			// Alphas (A–Z, upper and lower case).
    			else if (key >= 65 && key <= 90) {
    				/* … */
    				playSelect();
    			}
    		});
    	};
    })();
    


    Stylesheet (Twine 1: goes in stylesheet-tagged passage; Twine 2: goes in Story Stylesheet)
    body.noui #ui-bar {
    	display: none;
    }
    body.noui #story {
    	margin: 0 auto;
    }
    
    /*
    	#rpgmenu cursor menu
    */
    #rpgmenu {
    	background-color: #ddd;
    	width: 300px;
    	margin: auto;
    	position: relative;
    	padding: 5px 35px;
    }
    #rpgmenu ul {
    	list-style: none inside none;
    	padding: 0;
    	font-size: 40px;
    }
    #rpgmenu .pointer:before {
    	content: url("http://static2.wikia.nocookie.net/__cb20121113193557/finalfantasy/images/1/1b/FF1_PSP_cursor.png";);
    	position: absolute;
    	left: 0;
    }
    
    NOTE: You'll need to remove the spurious semi-color inside the closing parenthesis of the url() that the forums added.

    Menu markup
    <div id="rpgmenu">
        <ul>
            <li>[[New Game]]</li>
            <li><<link "Continue">>/* Do something here. */<</link>></li>
            <li><<link "Quit">>/* Do something here. */<</link>></li>
        </ul>
    </div>
    


    PS: I hope you have permission to use the assets you are or they're only temporary. Unauthorized hot linking to others' resources, public or not, is frowned upon.
  • greyelf said:
    I am going to assume you change the main passage content to something like the following when you changed your PassageDone to use getElementsByClassName:
    snip
    <div class="big-box">
    <ul>
    <li class="mymenu">New Game</li>
    <li class="mymenu">Continue</li>
    <li class="mymenu">Quit</li>
    </ul>
    </div>

    Yes!
    The problem is quite simple, at the end of the code that handles each key-press you empty the current class property values of your li elements.

    for(var i=0; i<cells.length; i++){ cells.className = "" }

    This means that after the first key-press none of those elements has a class of mymenu, this in turn means the cells variable will contain an empty array which will result in an error when you try to access the cursorposition element of the array.

    So, I changed
        for(var i=0; i<cells.length; i++){ cells[i].className = "" }
    

    to
        for(var i=0; i<cells.length; i++){ cells[i].className = "mymenu" }
    

    and it works. Awesome!

    Is this correct, or just luck?

    I do want to be able to have a class on the menus, as there may be more than one on the page, especially at character generation.

    Thank you, so much!
  • TheMadExile said:
    Your primary issue is that you're selecting all list item nodes on the page, rather than only the ones belonging to your menu. Beyond that, you have several other issues with your code.

    I'd suggest using something like the following as a starting point. As-is, pressing enter/return or spacebar will activate the menu links—in addition to clicking on them, naturally.

    Wow, you know exactly what I want to do. Thank you, for taking the time to work this out. Only I must have done something off, because it is not working for me. At first, I thought it might be the cursor, so I loaded my local cursor, but it made no change. The script does not appear to be read, as the arrow keys still scroll the page.

    I am attaching the test tws. I am sure I just did something dumb or missed a ; or }, but I cannot see it.
  • edited April 2017
    I forgot to include the updated PassageDone in my last post. That's probably your problem.

    PassageDone
    <<if passage() is "Start" or passage() is "menu_test">>
    	<<script>>setup.rpgMenu();<</script>>
    <</if>>
    

    I could have added a postdisplay task to the module, which would do the same without the need for PassageDone, however, I don't know where you eventually plan to place to the menu and I didn't want to make that bit difficult for you to find.
  • edited April 2017
    That did it!
    Awesome Possum. It works well. Many thanks to you and greyelf.

    Oh, and no, I will not be hotlinking. It was just because the fiddle had that and it made it simpler to explain.:)


Sign In or Register to comment.