Howdy, Stranger!

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

How to convert javascript to work in Twine

Okay, so I have managed to modify and tweak some existing javascript to achieve an effect I would like to use in Twine as a custom macro (discussed in this topic lhere).  I have tried looking  at the tutorial posted here but I'm kind of confused as it looks like I need to alter the existing javascript to get it working in Twine? 

This is the current code:
$('.inlinechoice').on('click','.question', function(e) {
var $this = $(this),
$id = $this.prop('id'),
$class = '.' + $('.answer-' + $id).prop('class').replace('hide', '');

$('.answerblock').hide();
$('.answer-' + $id).show();
$('div[class*=answer]').not($class).hide();
});
Codepen version here: http://codepen.io/anon/pen/HkCgG

Basically it allows the user to set up a number of clickable links, and when the user clicks on one of the links, the links disappear and an appropriate response shows in its place. 

I think the code should look something like this:
try {
version.extensions['fakechoice'] = { major:1, minor:0, revision:0 };
macros['fakechoice'] = {
handler: function(place, macroName, params, parser) {
[...]
};
} catch(e) {
throwError(place,"recall fakechoice Error: "+e.message);
}
But with the javascript sitting in the [...] somehow. Also, I'm not sure if I need more than one macro, i.e. one to set up the inlinechoice, and another to set up the answerblock.

I'm also unsure as to whether I also need to include
//requires jquery
//requires Modernizr
but when I share the javascript from codepen, I can see it makes reference to both libraries. 

Sorry if this seems similar to my other topic, but I suppose this is more focused on actually using javascript in Twine, so hopefully it's okay to make the post.  Advice and thoughts appreciated as always, thanks in advance! 

Comments

  • Hmm, well I didn't manage to figure out how to write this as a custom macro, but I did manage to find out how to use call an external javascript which seems to be doing the trick.

    For future reference, I found out how to do this here.  Basically, to call custom javascript, you need to save the script as a .js file in the same folder as your Twine game, then add this macro as a script passage.
    macros['loadJS'] =
    {
    handler: function(place, object, parameters)
    {
    var se = document.createElement("script");
    se.type = 'text/javascript';
    se.src = parameters[0];
    var hT = document.getElementsByTagName("HEAD")[0];
    hT.appendChild(se);
    if(se.innerText) {eval(se.innerText);}
    else {eval(se.textContent);}
    }
    }
    Then, when you want to call your javascript in Twine, you just type into a passage:
    <<loadJS yourscriptname.js>>
    In my case I also went into the 'Story>Special Passages>StorySettings menu and set both jQuery and Modernizr to on. 

    Apologies if this is repeating common knowledge, but I couldn't find this in the wiki/forum or googlegroups, nor was it obvious anywhere else.

    It'd still be nice to change this to a proper twine macro, as ultimately I'm not convinced my javascript is any less clumsy than the combination of using Leon's revision macros for the task but it's doing what I want it to, so that's pretty cool.    :)
  • bawpie wrote:
    Apologies if this is repeating common knowledge, but I couldn't find this in the wiki/forum or googlegroups, nor was it obvious anywhere else.


    Yeah, some of that is news to me. Good post!
  • You don't need to go to the effort to put the code in a separate file. Just put the code in a passage, label it "script", and then <<display>> the passage whenever you wish to re-run it. (Obviously it also runs on startup, but in this specific case that's not important. Maybe I'll add an additional "deferred" tag that can prevent this. Maybe not.)

    Modernizr is used almost exclusively for HTML5 and CSS3 cross-browser compatibility. It's not required in this case, let me assure you.
  • Thanks, but I've tried that and it doesn't seem to work?

    I've added the script to a script passage (brown header) and called it fakechoice.

    Then I've added <<display fakechoice>> and an arrow joins the game passage to the script passage.  But it doesn't seem to work (basically I can't click the text and get the response). 

    https://dl.dropboxusercontent.com/u/11379019/Twine/scripttest.tws

    I guess I'm doing something wrong?
  • Well. I'm going to bump this to see if I can get any advice.  I got my javascript to function pretty much how I want it to, codepen example here.  I don't think the javascript itself is too complicated:
    $('.inlinechoice').on('click','.question', function(e) {
    var $this = $(this),
    $id = $this.prop('id'),
    $class = '.' + $('.answer-' + $id).prop('class').replace('hide', '');

    $('.answerblock').hide();
    $('.answer-' + $id).show();
    $('div[class*=answer]').not($class).hide();
    });
    But, I would really like to write this as a twine macro, which will also allow me to set variables as well.  Nothing else.  Unfortunately all of the tutorials I've found have been for 'simple' pieces of javascript, but I've tried looking at some of L's examples but they are way over my head.  CoraBlue seems to have some good examples posted here which I think could help, but if anyone had any suggestions or advice it'd be much appreciated.


  • Well, I've tried and failed miserably to get anywhere with this over the past week.  So even if I could adapt the existing javascript to pass variables into twine, I think that would help as whilst I don't want to create new nodes based on a choice, I would like to be able to record what choices were made.  I got about this far with my custom macro, which basically replaces one link with some text...but trying to take it to the next level and replace multiple links with the appropriate answer is well beyond me.

    //usage: <<linky "Hello There!" 1 >> "It's good to see you!" <<endlinky>>

    version.extensions["linkymacros"] = { major: 1, minor: 0, revision: 0 };
    macros["linky"] =
    {
    handler: function(place, macroName, params, parser)
    {
    if (params.length === 0)
    {
    throwError(place, "<<" + macroName + ">>: no dialog source (speaker) specified");
    return;
    }

    // process the contents of the container macro
    var openTag = macroName
    , closeTag = "end" + macroName
    , start = parser.source.indexOf(">>", parser.matchStart) + 2
    , end = -1
    , tagBegin = start
    , tagEnd = start
    , opened = 1;
    while ((tagBegin = parser.source.indexOf("<<", tagEnd)) !== -1
    &amp;&amp; (tagEnd = parser.source.indexOf(">>", tagBegin)) !== -1)
    {
    var tagName = parser.source.slice(tagBegin + 2, tagEnd)
    , tagDelim = tagName.search(/\s/);
    if (tagDelim !== -1)
    {
    tagName = tagName.slice(0, tagDelim);
    }
    tagEnd += 2;
    switch (tagName)
    {
    case closeTag:
    opened--;
    break;
    case openTag:
    opened++;
    break;
    }
    if (opened === 0)
    {
    end = tagBegin;
    break;
    }
    }

    // if we successfully found an end tag for the macro
    if (end !== -1)
    {
    parser.nextMatch = tagEnd;

    var linktoclick = params[0];

    var linkchoice = params[1];

    var linktext = linktoclick + '<br>' + parser.source.slice(start, end);

    em = document.createElement('b');
    em.id ="b";

    el = document.createElement('a');
    el.className = 'internalLink';
    el.href = "javascript:void(0)";
    el.innerHTML = params[0];

    el.onclick = function(){

    //this replaces the original choice with the answer contained in the open and closing tags
    el.parentNode.replaceChild(em,el);

    new Wikifier(place, linktext);
    };

    //this makes the original choice visible

    place.appendChild(el);


    }
    else
    {
    throwError(place, "<<" + macroName + ">>: cannot find a matching close tag");
    }
    },
    };
    macros["endlinky"] = { handler: function () {} };
  • Well, on the off chance that this could ever be useful to anyone else, I figured out how to pass variables from the javascript back into Twine although it only picks up the choice when you change passages...so I guess the state.history only updates once you leave the passage? 


    $('div[class*=inlinechoice').on('click','.question', function(e) {
    var $this = $(this),
    $id = $this.prop('id');

    $('.answer-' + $id).show();
    state.history[0].variables['choice_id'] = $this.prop('id');
    });

    $('div[class*=inlinechoice').on('click', function(a) {
    var $this = $(this),
    $cid = $this.prop('id');

    $('.inlinechoice-' + $cid).hide();
    });
    The Twine passage looks like this:
    <<loadJS fakechoice1.js>>

    Pick an option:<br><br>

    <div class="inlinechoice-0" id="0">
    <a class="question" id="1">Choice 1</a><br>
    <a class="question" id="2">Choice 2</a><br>
    <a class="question" id="3">Choice 3</a>
    </div>

    <div class="answer-1">
    You picked 1.
    <div class="inlinechoice-1" id="1">
    <a class="question" id="4">Choice 4</a><br>
    <a class="question" id="5">Choice 5</a><br>
    </div>
    </div>
    <div class="answer-2">
    You picked 2
    </div>
    <div class="answer-3">
    You picked 3
    </div>
    <div class="answer-4">
    You picked 4
    <div class="inlinechoice-2" id="2">
    <a class="question" id="6">Choice 6</a><br>
    <a class="question" id="2">Choice 7 (2)</a><br>
    </div>
    </div>
    <div class="answer-5">
    You picked 5
    </div>
    <div class="answer-6">
    You picked 6
    </div>
    <br><br>
    <FORM>
    <INPUT TYPE="button" onClick="history.go(0)" VALUE="Refresh">
    </FORM>

    Check [[variables]]
    Edit:  I tried changing the code to this:

    $('div[class*=inlinechoice').on('click','.question', function(e) {
    var $this = $(this),
    $id = $this.prop('id');

    $('.answer-' + $id).show();

    var $arr = [];

    $arr.push($id);

    state.history[0].variables['choice_id'] = $arr;
    });

    $('div[class*=inlinechoice').on('click', function(a) {
    var $this = $(this),
    $cid = $this.prop('id');

    $('.inlinechoice-' + $cid).hide();
    });
    As I'd like to track all choices made when clicking, but when checking the variables in the next passage, it's only storing the last choice.  Any ideas?
  • Figured it out...I was setting my array to 0 every time I did an onclick....oops!  Instead I declared my array variable outside of the onclick and it seems to have done the trick.  Anyway, although I can't track the choices in passage, this allows me to check them in the next passage, which should be good enough for setting some variables based on any responses.  Anyway, new javascript in case it's of any interest to anyone.
    var $arr = []

    $('div[class*=inlinechoice').on('click','.question', function(e) {
    var $this = $(this),
    $id = $this.prop('id');


    $arr.push($id);
    state.history[0].variables['choice_id'] = $arr.join(', ');

    $('.answer-' + $id).show();
    });

    $('div[class*=inlinechoice').on('click', function(a) {
    var $this = $(this),
    $cid = $this.prop('id');

    $('.inlinechoice-' + $cid).hide();


    });

    Edit: Made more tweaks to the code now, which will pick up clicked choices in order.
    var $arr = []

    $('div[class*=inlinechoice']).on('click','.question', function(e) {
    var $this = $(this),
    $id = $this.prop('id');

    $arr.push($id);
    state.history[0].variables['choice_id'] = $arr.join(', ');

    $("#dialogue_container").append($('.answer-' + $id).contents());

    });

    $('div[class*=inlinechoice']).on('click', function(a) {
    var $this = $(this),
    $cid = $this.prop('id');

    $('.inlinechoice-' + $cid).hide();


    });
    Working codepen here.
  • just wanted to say thanks to bawpie for posting all this - I may use the external .js instead of wrangling with the <<display>> 'script' technique for my stuff...partly since the twine editor is so wonky...

Sign In or Register to comment.