Howdy, Stranger!

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

Custom macros: How to set multiple variables onclick

Okay, so when clicking a standard twine link, we are able to set multiple variables using the following syntax, as discussed here.

[[displayed text|title of passage][$variable = expression; $variable = expression]]

My question is, how would I achieve that in a custom macro?  Currently, I've managed to put something together (after looking at many of Leon Arnott's macros admittedly) that allows me to set one variable onclick, but not any more than that (any additional variable changes are ignored).  This is the code I'm using:
macros["question"] =
{
handler: function(place, macroName, params, parser)
{
if (params.length === 0)
{
throwError(place, "<<" + macroName + ">>: no question id 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;

// Macrocode
this.a = macroName;
this.b = params[0];
this.c = parser.source.slice(start, end);

this.d = ('<a class="question" id="' + this.b + '">' + this.c + '</a>');

//new Wikifier(place, this.d);

checktweevar = params[1];
if(checktweevar == null){}
else
{
var tweevar = params[1].replace("$","");
}
var value = params[2];

if(value == null){}
else
{
if (value.charAt(0)=="$") {
value = eval(value.replace("$","state.history[0].variables."));
}
}

el = document.createElement('a');
el.className = 'internalLink';
el.href = "javascript:void(0)";
el.innerHTML = this.d;

el.onclick = function(){

state.history[0].variables[tweevar] = value;

};
place.appendChild(el);


}
else
{
throwError(place, "<<" + macroName + ">>: cannot find a matching close tag");
}
},
};
macros["endquestion"] = { handler: function () {} };
Note, this is part of a larger macro, so literally all this allows you to do is click a link but nothing seems to happen, in the background though it will change the variable.

So whilst something like this will work:
<<question 1 $foo "bar">>
Something like this doesn't:
<<question 2 $foo "foo"; $bar "foo">>
In the second example, $foo is set to "foo", but $bar remains unchanged.  I expect I need to change how my code works, but I'm not even sure where to start, apart from trying to find the code behind how [[link]]'s work in the twine source code, which is probably where I'll look next.

HTML example
TWS example

Comments

  • Well, I'm definitely stumped. 

    With <<set>> I know you can do this:  <<set $test = 3; $test2 = 6>>

    So I figured I'd look at the in built macro for <<set>> which I think I found here
    version.extensions.setMacro = {
    major: 1,
    minor: 1,
    revision: 0
    };
    macros.set = {
    handler: function (a, b, c, parser) {
    macros.set.run(a, parser.fullArgs(), parser)
    },
    run: function (a,expression, parser) {
    try {
    return eval(expression);
    } catch (e) {
    throwError(a, "bad expression: " + e.message, parser ? parser.fullMatch() : expression)
    }
    }
    };
    But, it looks like it's using other inbuilt functions?  Alls I could find on fullargs was the description here which states that fullArgs is "Meant to be called by macros, this returns the text passed to the currently executing macro.  Unlike TiddlyWikis default mechanism, this does not attempt to split up the arguments into an array, thought it does do some magic with certain Twee operators (like gt, eq, and $variable)."  Not sure what the magic  is...but it appears to refer to this:
    Wikifier.prototype.fullArgs = function (includeName) {
    var source = this.source.replace(/\u200c/g," "),
    endPos = source.indexOf('>>', this.matchStart),
    startPos = source.indexOf(includeName ? '<<' : ' ', this.matchStart);
    if (!~startPos || !~endPos || endPos <= startPos) {
    return "";
    }
    return Wikifier.parse(source.slice(startPos + (includeName ? 2 : 1), endPos).trim());
    };
    So yeah, I think this is way over my head.  ???
  • Hmm... Couldn't you do what you want using L's <<replace>> macro, or <<replacelink>> in the Sugarcube version?

    Something like this:

    Bedroom passage:
    Let's <<replace>>jump on the bed<<becomes>><<display "jumplink">><<endreplace>>
    Jumplink passage:
    <<set $variable = 1; $variable2 = 1>><<replace>>jump on the bed<<becomes>><<display "jumplink">><<endreplace>>
    I haven't tested that, but it should work. I think. :P
  • Try this:

    &nbsp; macros.question = {<br />&nbsp; &nbsp; handler: function(place, macroName, params, parser) {<br />&nbsp; &nbsp; &nbsp; if (params.length % 2 == 1) {<br />&nbsp; &nbsp; &nbsp; &nbsp; throwError(place, &quot;&lt;&lt;&quot; + macroName + &quot;&gt;&gt;: odd number of parameters given&quot;);<br />&nbsp; &nbsp; &nbsp; &nbsp; return;<br />&nbsp; &nbsp; &nbsp; }<br />&nbsp; &nbsp; &nbsp; var left, right;<br />&nbsp; &nbsp; &nbsp; for (var index = 0; index &lt; params.length; index i+= 2) {<br />&nbsp; &nbsp; &nbsp; &nbsp; var s;<br />&nbsp; &nbsp; &nbsp; &nbsp; if (isNaN(params[index+1])) {<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s = &quot;state.active.variables.&quot; + params[index] + &quot; = &#039;&quot; + params[index+1] + &quot;&#039;&quot;;<br />&nbsp; &nbsp; &nbsp; &nbsp; } else {<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; s = &quot;state.active.variables.&quot; + params[index] + &quot; = &quot; + params[index+1];<br />&nbsp; &nbsp; &nbsp; &nbsp; }<br />&nbsp; &nbsp; &nbsp; &nbsp; eval(s);<br />&nbsp; &nbsp; &nbsp; }<br />&nbsp; &nbsp; },<br />&nbsp; };

    The problem with it is that your variables cannot have the dollar sigel in from of them. So this example sets $d, $e and $f.

    &lt;&lt;question d 45 e 13 f house&gt;&gt;<br />&lt;&lt;print $d + 5&gt;&gt;<br />&lt;&lt;print $e&gt;&gt;<br />&lt;&lt;print $f&gt;&gt;

    Variables seem to extracted from the parameter list for some reason, and I cannot imagine why. However, entering them as text, without the dollar, gets around that.

    ETA: Thinking about it, the variables get substituted first, then the macro is invoked, so if I did
    <<question $d 45 $e 13 $f house>>
    Twee would try to pass the value of $d, etc.

    ETA2: This works for sugarcube. For others, "state.active.variables." needs to be "state.history[0].variables." (two changes).
  • Trying to work out how to do this resulted in a blog post on macros that you might find useful:

    http://strugglingwithtwine.blogspot.co.uk/2014/03/how-to-write-macros.html
  • @ mostly useless:  Whilst that would work for an isolated case, the plan is to incorporate this into a larger macro (as demonstrated here).  Thanks though :)

    @ The Pixie:  Thanks for this, though I've tried it in Sugarcane and I just get an error about question not being recognised?  I'm using this in a script passage:
    macros.question = {
    handler: function(place, macroName, params, parser) {
    if (params.length % 2 == 1) {
    throwError(place, "<<" + macroName + ">>: odd number of parameters given");
    return;
    }
    var left, right;
    for (var index = 0; index < params.length; index i+= 2) {
    var s;
    if (isNaN(params[index+1])) {
    s = "state.history[0].variables." + params[index] + " = '" + params[index+1] + "'";
    } else {
    s = "state.history[0].variables." + params[index] + " = " + params[index+1];
    }
    eval(s);
    }
    },
    };
    HTML Example| TWS Example

    Re the $ sigil issue, I don't know if I read somewhere else that the $ is used in javascript regular expressions to denote the end of a string, so there might be some conflict.  By using something like params[1].replace("$","") I think you can get around it though.

    I'll be sure to read through that link in a bit, I've read plenty of macro twine tutorials, but I think they're all aimed way above my level.   
  • bawpie wrote:

    I've tried it in Sugarcane and I just get an error about question not being recognised?  I'm using this in a script passage:


    There's an erroneous "i" in the loop expression section of the for loop.

    FIND:

    for (var index = 0; index < params.length; index i+= 2) {
    REPLACE WITH:

    for (var index = 0; index < params.length; index += 2) {
  • Ah, cool, that's fixed it thanks!  ;D
  • There seems to be some confusion about referencing $variables (for modification by macros).  The vanilla headers and SugarCube have different requirements here, stemming from a basic difference in how $variables passed to macros are handled.

    In SugarCube:

    [quote=http://www.motoslave.net/sugarcube/docs/#macros]Note: Variable substitution occurs automatically in SugarCube, so all macros that take arguments can take $variables as arguments.

    As evidenced by the above quote, SugarCube is based on the premise that the normal use of $variables as arguments to macros should not require any processing by the macros themselves (i.e. all macros should automatically receive $variable value substitution for free), and so SugarCube's macro formatter does just that.  This is why, when using macros in SugarCube where you want to, in essence, pass a reference to the $variable, rather than its value, you must either quote it or give it without the dollar-sign ($) sigil (SugarCube's own <<textbox>> macro spells that out).  To use the existing example:

    // these are both okay, no substitution will occur
    <<question d 45 e 13 f house>>
    <<question "$d" 45 "$e" 13 "$f" house>>

    // this is not, since the $variables will be substituted for their values
    <<question $d 45 $e 13 $f house>>

    In the vanilla headers:

    No automatic $variable value substitution occurs, so there's no need to quote, or otherwise protect, $variable identifiers.  Of course, this also means that every single macro which wants to support $variable substitution has to do that processing themselves.
  • Thanks for that explanation, I'd come across a few examples where the $ seemed to be replaced with " ", so I that would explain why.  Out of curiosity, how would that variable substitution processing be applied in The Pixies code?  I thought I could do something like params.replace("$","") might work, but I doubt it'd be that simple. 

    I managed to get The Pixie's code working with my own, and it seems to be working pretty well, so thanks both - I posted a working example in this post. 
  • TheMadExile wrote:

    There's an erroneous "i" in the loop expression section of the for loop.

    Oops. I used "i" originally to test, but when I posted it here, the i in square brackets was interpreted as italics by the BBCode, and disappeared, so I quickly changed it to index; guess I missed one.
  • Okay so this seems to be working quite nicely but...

    If I want to do this:  <<question var +5>>  It will print as 5, regardless of the value of $var.  So if $var = 5, I would expect to see 10 when I do <<print $var>> but it just shows 5.  Any way around this?  I tried to just get it to automatically add the value to the existing total of $var if it was a number using something like this:
    macros['test'] = {
    handler: function(place, macroName, params, parser) {

    var foo = state.history[0].variables.foo;
    foo = foo + +(params[0]);
    state.history[0].variables.foo = foo;

    },
    init: function() { },
    };
    , and that seems to work (i.e. adds the value of test whenever called) but I couldn't work it into the above macro.

    Edit:  Posted this as a seperate question here.
Sign In or Register to comment.