Howdy, Stranger!

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

SugarCubing a custom container macro

edited March 2014 in Help! with 1.x
The container macro processing discussed here inspired me to implement a recurrent feature in my own WIP in similar fashion. The idea is that you create a little table of links to further information ("sidebars") inside the <<sidebar-box>> macro:
<<sidebar-box "Questions">>\
<<sidebar-link>>[[Should I shun the frumious bandersnatch?|Shun It]]<</sidebar-link>>\
<<sidebar-link>>[[Are tumtum trees good loci for napping?|Resting]]<</sidebar-link>>\
<</sidebar-box>>\
Basically, the sidebar-box macro creates a structure of two divs, one a header and the other a container; the <<sidebar-link>> items are wrapped in <p> tags and placed in the latter div. (I want to use <p> rather than <br> so that I can control line spacing via CSS--otherwise no special <<sidebar-link>> macros would be necessary!)

The code I came up with (below) works just fine, stealing as it does the hard part (i.e., the tag-parsing code) from the macro linked above.  While the code works, I wonder if SugarCube has easier ways of doing this. I wonder this in two senses:

1) Is there a way to use the this.payload API call to greatly simplify the parsing of subtags? this.payload returns null for the code below--see the console.log call--so I'm not sure where to start with that, really... Maybe I shouldn't be manually handling end tags (see end of macro code below)? But while SugarCube can handle the end tags automatically, this.payload is still null if I allow it to do that.
2) Do I really need any nested tags? Couldn't I just do away with the <<sidebar-link>> macro and just have <<sidebar-box>> wrap each link or line in a <p> tag? If that's not complicated, how would I go about doing it?

I also have one subsidiary question: You'll notice that I have used backslashes at the end of each line in the twee code above. This is because not doing so results in <br> tags being inserted into the final output. Is there a way to suppress <br> from within the macro's handler (i.e. in javascript) as opposed to wrapping the twee code in <<nobr>>? Maybe an argument that can be passed to the Wikifier...?
(function () {
macros.add(["sidebar-box", "sidebar-link"], {
handler: function() {
// process the contents of the container macro
console.log(this.name);
console.log(this.payload);
var macroName = this.name,
parser = this.parser,
openTag = macroName,
closeTag = "/" + 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 wikitext = parser.source.slice(start, end);
switch (macroName)
{
case "sidebar-link":
wikitext = '<p>' + wikitext + '</p>';
break;

case "sidebar-box":
wikitext = '<div class="' + macroName + '"><div class="sidebar-head">' + (this.args[0] ? this.args[0] : 'More') + '</div><div class="sidebar-link">' + wikitext + '</div></div>';
break;
}
new Wikifier(this.output, wikitext);
}
else
{
return this.error("cannot find a matching close tag");
}
}
});
macros.add(["/sidebar-box", "/sidebar-link"], { handler: function () {} });
}());

Comments

  • Erik wrote:

    Basically, the sidebar-box macro creates a structure of two divs, one a header and the other a container; the <<sidebar-link>> items are wrapped in <p> tags and placed in the latter div. (I want to use <p> rather than <br> so that I can control line spacing via CSS--otherwise no special <<sidebar-link>> macros would be necessary!)


    Is there some reason you can't use the CSS line-height property to control the spacing of the links?

    Beyond that, why do you want to use paragraph elements to encapsulate a list of links anyway?  If you have an unordered list of items, why not use the unordered list element to represent that list (that's what it's for after all).  You can use CSS to style that how you'd like and it would be better semantically.


    Erik wrote:

    The code I came up with (below) works just fine, stealing as it does the hard part (i.e., the tag-parsing code) from the macro linked above.  While the code works, I wonder if SugarCube has easier ways of doing this.


    I strongly advise against mixing old- and new-style macro code.  And yes, there's an easier way to do what you want.


    Erik wrote:

    1) Is there a way to use the this.payload API call to greatly simplify the parsing of subtags?


    Sub-tags of a parent macro, yes (that's what it's for after all).  Independent "partner" macros meant to be used in conjunction with another macro, which is what you are doing currently, no.


    Erik wrote:

    1) this.payload returns null for the code below--see the console.log call--so I'm not sure where to start with that, really... Maybe I shouldn't be manually handling end tags (see end of macro code below)? But while SugarCube can handle the end tags automatically, this.payload is still null if I allow it to do that.


    The payload sub-object only exists if the macro is a container macro.  You specify that a macro is a container macro via the tags property of the macro definition, which is also how you specify child tags.
    [quote=http://www.motoslave.net/sugarcube/docs/macro-api.html#macro-management-api-method-add]tags: (null | string array) Signifies the macro is a container macro. Null, if there are no child tags, or an array of the names of the child tags.

    You did not specify the tags property, so the macro isn't considered a container macro and thus no payload sub-object was created for it.

    And no, you should not be manually registering the closing tags.  If the macro is a container macro, macros.add() will register both types of closing tag automatically, along with any child tags.  (On the off chance you wanted to do so, you wouldn't register tags via macros.add() anyway.  There's a separate method that actually handles tag (de)registration.)


    Erik wrote:

    2) Do I really need any nested tags? Couldn't I just do away with the <<sidebar-link>> macro and just have <<sidebar-box>> wrap each link or line in a <p> tag? If that's not complicated, how would I go about doing it?


    For what you want to do, you need neither child tags nor partner macros.  So, yes, things could be simplified down to just a <<sidebar>> macro.


    Erik wrote:

    I also have one subsidiary question: You'll notice that I have used backslashes at the end of each line in the twee code above. This is because not doing so results in <br> tags being inserted into the final output. Is there a way to suppress <br> from within the macro's handler (i.e. in javascript) as opposed to wrapping the twee code in <<nobr>>? Maybe an argument that can be passed to the Wikifier...?


    Yes, you can do that with replace() or split() (I use split() in the code below, since I needed a list anyway).

    Example code: (uses an unordered list for the link container, two comments explain how to switch to paragraphs if you're wedded to that idea)

    macros.add("sidebar", {
    version: { major: 1, minor: 0, revision: 0 },
    tags: null,
    handler: function ()
    {
    if (this.payload.length !== 0)
    {
    // create the basic elements
    var sidebar = document.createElement("div")
    , header = document.createElement("div")
    , links = document.createElement("ul"); // use "div" for paragraph elements

    // setup the basic elements
    sidebar.className = this.name;
    header.className = this.name + "-header";
    header.innerHTML = this.args.length !== 0 ? this.args[0] : "More";
    links.className = this.name + "-links";
    sidebar.appendChild(header);
    sidebar.appendChild(links);

    // process the payload
    var wikitext = this.payload[0].contents.trim().split(/\s*\n\s*/);
    for (var i = 0; i < wikitext.length; i++)
    {
    var linkEl = document.createElement("li"); // use "p" for paragraph elements
    new Wikifier(linkEl, wikitext[i]);
    links.appendChild(linkEl);
    }

    // append the sidebar to the output buffer
    this.output.appendChild(sidebar);
    }
    }
    });
    HTML structure:

    <div class="sidebar">
    <div class="sidebar-header"> header text </div>
    <ul class="sidebar-links">
    <li> link 1 </li>
    <li> link N </li>
    </ul>
    </div>

    <!-- Paragraph version would look like this -->
    <div class="sidebar">
    <div class="sidebar-header"> header text </div>
    <div class="sidebar-links">
    <p> link 1 </p>
    <p> link N </p>
    </div>
    </div>
    Usage: (the only strict rule is that each link must be on its own single line)

    <<sidebar "Questions">>
    [[Should I shun the frumious bandersnatch?|Shun It]]
    [[Are tumtum trees good loci for napping?|Resting]]
    <</sidebar>>

    /% The code trims all space at the beginning and end of each link line, so this also works as desired %/
    <<sidebar "Questions">>

    [[Should I shun the frumious bandersnatch?|Shun It]]

    [[Are tumtum trees good loci for napping?|Resting]]

    <</sidebar>>
  • Wow, thanks for all that. Lots to learn from (I'd forgotten all about tags defining container macros and alternate names, which is the main reason why my code was basically a mix of old and 'cube macro styles; should have read the macro API docs more carefully).

    The only reason for avoiding an unordered list structure was to minimize nesting, but that's silly in retrospect: the <ul> code is not complexly nested and could easily be placed by the <<sidebar>> macro as well--as you've done in your example code!
  • By the way, the sugarcubed code is *so* much nicer and easier to follow.  :)
Sign In or Register to comment.