Howdy, Stranger!

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

Append HTML/Twine code with widget/javascript?

Using Sugarcube 2

I have a div in my passage,
<div id="myButtons></div>
and a template for a button,
<span class="btn"><<button "%value%>><<set $selected to "%value%">></span>

I have an array of values,
var values = ["high", "medium", "low"];

I'dd like to generate a button for each of those and add to to the div "myButtons".

So far I tried doing it in Javascript with a postdisplay, but the Twine code isn't interpreted. Any other Task Objects don't do anything.

I'm not too familiar with Twine, but I know Javascript. How do I properly "inject" my generated buttons?

Comments

  • The easiest thing would probably be to print the markup using <<for>>:
    <div id="myButtons">\
    <<set _values to ["high", "medium", "low"]>>\
    <<for _i to 0; _i < _values.length; _i++>>\
    <<='<span class="btn"><<button "' + _values[_i] + '">><<set $selected to "' + _values[_i] + '">></span>'>>\
    <</for>>\
    </div>
    


    The next easiest thing would probably be to print the markup using a function: (Twine 1: goes in a script-tagged passage; Twine 2: goes in Story JavaScript)
    window.createButtonMarkup = function (/* variadic */) /* string */ {
    	var list = Array.prototype.concat.apply([], arguments);
    	var tpl  = '<span class="btn"><<button "{0}">><<set $selected to "{0}">></span>';
    
    	return list
    		.map(function (val) { return String.format(tpl, val); })
    		.join('');
    };
    
    Usage:
    /* With loose parameters. */
    <div id="myButtons"><<=createButtonMarkup("high", "medium", "low")>></div>
    
    /* Also accepts arrays. */
    <div id="myButtons"><<=createButtonMarkup(["high", "medium", "low"])>></div>
    


    Untested, but either of those should work.


    PS: You have a few missing quotes in your examples. You might want to keep an eye on that.
  • edited August 2016
    I created a solution by incorporating the second solution into a <<widget>>
    <<widget "autoGenerateHtml">>\
        <<set $autoGenerateHtml_values = $args[0]>>\
        <<set $autoGenerateHtml_templateCode = $args[1]>>\
        <<for $autoGenerateHtml_idx = 0; $autoGenerateHtml_idx < Object.keys($autoGenerateHtml_values).length; $autoGenerateHtml_idx++>>\
            <<set $autoGenerateHtml_htmlCode = 0>>\
            <<script>>
                var template = variables().autoGenerateHtml_templateCode;
                var idx = variables().autoGenerateHtml_idx;
                var key = Object.keys(variables().autoGenerateHtml_values)[idx];
                var val = variables().autoGenerateHtml_values[key];
                variables().autoGenerateHtml_htmlCode = template.replace(/%val%/g, val).replace(/%valUc%/g, val.ucfirst()).replace(/%key%/g, key);
            <</script>>\
    \
            <<if $autoGenerateHtml_htmlCode.length > 1>>\
                <<= $autoGenerateHtml_htmlCode>>\
            <</if>>\
        <</for>\
        <<forget $autoGenerateHtml_idx,\
              $autoGenerateHtml_htmlCode,\
              $autoGenerateHtml_templateCode,\
              $autoGenerateHtml_values>>\
    <</widget>>\
    

    My main issue is that since I need to access these variables from Javascript, I need to declare them as state variables ($). But after I print the code, my <<forget>> statement does not remove them from the state. So I am stuck carrying those around.

    From what I see, I think I should switch to full-Javascript for this with
    macros["autoGenerateHtml"] = {
        handler: function(place, macroName, params, parser) {
            var code;
            var list = params[0];
            var template = params[1];
            ...process list and template into code...
            new Wikifier(place, code);
        },
        init: function() {}
    };
    

    Is Wikifier the equivalent of "<<=...>>"? The name throws me off, I am wondering if there is any kind of catch with that.
  • edited August 2016
    The Wikifier class is responsible for converting the passed code ('text' containing markup and/or HTML tags) into HTML elements and then injecting those HTML elements into the DOM, it is used by all macro's that generate output which includes the <<print>> (<<=>>) macro.
  • You might want to brush up on SugarCube's documentation (for v2) as you've made some fairly basic mistakes.
    sssk wrote: »
    <<widget "autoGenerateHtml">>\
        <<set $autoGenerateHtml_values = $args[0]>>\
        <<set $autoGenerateHtml_templateCode = $args[1]>>\
        <<for $autoGenerateHtml_idx = 0; $autoGenerateHtml_idx < Object.keys($autoGenerateHtml_values).length; $autoGenerateHtml_idx++>>\
            <<set $autoGenerateHtml_htmlCode = 0>>\
            <<script>>
                var template = variables().autoGenerateHtml_templateCode;
                var idx = variables().autoGenerateHtml_idx;
                var key = Object.keys(variables().autoGenerateHtml_values)[idx];
                var val = variables().autoGenerateHtml_values[key];
                variables().autoGenerateHtml_htmlCode = template.replace(/%val%/g, val).replace(/%valUc%/g, val.ucfirst()).replace(/%key%/g, key);
            <</script>>\
    \
            <<if $autoGenerateHtml_htmlCode.length > 1>>\
                <<= $autoGenerateHtml_htmlCode>>\
            <</if>>\
        <</for>\
        <<forget $autoGenerateHtml_idx,\
              $autoGenerateHtml_htmlCode,\
              $autoGenerateHtml_templateCode,\
              $autoGenerateHtml_values>>\
    <</widget>>\
    
    Issues:
    • You're trying to use <<forget>> where you should be using the <<unset >> macro, and to top it off you've erroneously placed line continuation markup inside the macro invocation—don't do that.
    • You're using story variables where you probably should be using temporary variables—which would obviate the need to unset them in the first place.
    • You're setting your HTML code string variable to 0 initially, rather than the empty string—that's an absolutely bizarre thing to do, BTW.
    • You're probably using an <<if>> that you don't need—the condition should never be false, unless you plan on passing in empty templates.
    • There is no <String>.ucfirst() method in the standard library or SugarCube's extensions, so I hope that's something you've added.

    May I suggest:
    <<widget "autoGenerateHtml">>\
        <<set
            _values   = $args[0],
            _keys     = Object.keys(_values),
            _template = $args[1]
        >>\
        <<for _i = 0; _i < _keys.length; _i++>>\
            <<set
                _key  = _keys[_i],
                _val  = _values[_key],
                _html = _template
                    .replace(/%val%/g, _val)
                    .replace(/%valUc%/g, _val.ucfirst())
                    .replace(/%key%/g, _key)
            >>\
            <<= _html>>\
        <</for>\
    <</widget>>
    
    You don't actually need the <<=>> macro here because of the naked variable markup, but I left it in since that's what you were using.

    sssk wrote: »
    My main issue is that since I need to access these variables from Javascript, I need to declare them as state variables ($). But after I print the code, my <<forget>> statement does not remove them from the state. So I am stuck carrying those around.
    You don't need to use JavaScript. TwineScript constructs, which have native access to both types of variables, work just as well in most cases—as I've shown above.

    Assuming for the moment that dropping down to pure JavaScript was necessary in this instance, you may access temporary variables via the TempVariables object and story variables via the State.variables object or the variables() story function, which returns a reference to the object—you, obviously, know about the latter.

    I've already explained what you did wrong with <<forget>>.

    sssk wrote: »
    From what I see, I think I should switch to full-Javascript for this with
    macros["autoGenerateHtml"] = {
        handler: function(place, macroName, params, parser) {
            var code;
            var list = params[0];
            var template = params[1];
            ...process list and template into code...
            new Wikifier(place, code);
        },
        init: function() {}
    };
    
    Full stop. I recognize that macro "template" and you can toss it into the garbage where it belongs.

    Read SugarCube's macro API.

    As I've shown above, what you want to accomplish is easily handled without dropping to pure JavaScript. If you want to, that's perfectly fine. You do not, however, need to.

    That said, I can provide an example of such a macro: (based on your example code)
    Macro.add('autoGenerateHtml', {
        handler : function () {
    		if (this.args.length < 2) {
    			var errors = [];
    			if (this.args.length < 1) { errors.push('values object'); }
    			if (this.args.length < 2) { errors.push('template'); }
    			return this.error('no ' + errors.join(' or ') + ' specified');
    		}
    
    		var
    			values   = this.args[0],
    			keys     = Object.keys(values),
    			template = this.args[1];
    
    		keys.forEach(function (key) {
    			var val = values[key];
    
    			new Wikifier(
    				this.output,
    				template
    					.replace(/%val%/g, val)
    					.replace(/%valUc%/g, val.ucfirst())
    					.replace(/%key%/g, key)
    			);
    		});
        }
    });
    

    If the "values" object you're passing in is actually an array, as in your first examples, then a better way to do this would be the following:
    Macro.add('autoGenerateHtml', {
        handler : function () {
    		if (this.args.length < 2) {
    			var errors = [];
    			if (this.args.length < 1) { errors.push('values array'); }
    			if (this.args.length < 2) { errors.push('template'); }
    			return this.error('no ' + errors.join(' or ') + ' specified');
    		}
    
    		var
    			values   = this.args[0],
    			template = this.args[1];
    
    		values.forEach(function (val, i) {
    			new Wikifier(
    				this.output,
    				template
    					.replace(/%val%/g, val)
    					.replace(/%valUc%/g, val.ucfirst())
    					.replace(/%key%/g, i)
    			);
    		});
        }
    });
    
    Because using Object.keys() on an array is just plain silly.

    sssk wrote: »
    Is Wikifier the equivalent of "<<=...>>"? The name throws me off, I am wondering if there is any kind of catch with that.
    Not quite. The <<=>> macro is an alias of the <<print>> macro. The reason it works is because the printed value is passed through the Wikifier.
Sign In or Register to comment.