Howdy, Stranger!

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

Performing a macro AFTER page displays

I need to perform a macro after the passage has already been loaded.

I can think of a million different reasons you'd want to do this, but off the top of my head, say you wanted to get the font size of the place in which you launched the macro.
state.active.variables.fontsize = $("#"+place.id).css('font-size')
new Wikifier(place, ("The font size of this very spot is" + state.active.variables.fontsize));
If you do this inside a normal macro, it's going to execute immediately (i.e. before the passage loads) and you'll get the div properties of the last page, not the current one. I've been getting around it by wrapping the macro inside a setTimeout(function(){},0).

Doing it this way though, there's occasionally a brief millisecond of lag. It can make entire passages or paragraphs look like they flash into existence out of nowhere. Is there a better way (tiddlywiki specific) to make sure the passage content is available before executing a macro?

I continue to thank everyone here who can answer these questions, which I realize are starting to get a lot more technical.

Comments

  • Assuming you're still using SugarCube, this is what the PassageDone special passage does--any code in it runs immediately after the passage loads. (There's also PassageReady, which runs immediately before the passage is rendered.)
  • PassageDone and PassageReady are great, yes. But they'll run their contents every single time a page is loaded. What I need to do is tell a macro that I put inside a unique passage to wait to execute it's javascript until the page elements are available.
  • Once the passage has rendered, it's not going to update again except in response to an event (e.g. the timer event in your example). I'd suggest putting code in PassageDone to check for the condition you want for the passage to fire (e.g.
    <<if state.active.title eq "Special Passage">>
    , and then <<display>> your unique passage.
  • There's a state.display() at the end of the macro, so that's not an issue. And again, this macro needs to work on whatever passage I put it on for a rather large project. I can't add if checks to PassageReady for every passage.

    That doesn't even take into account the fact that PassageReady doesn't even work for what you're describing. Try this in PassageReady or PassageDone:
    <<script>>
    alert("Pausing javascript");
    <<endscript>>
    Your alert is going to appear before the passage loads. Your passage will be blank when you see it every time.
  • Er, I didn't suggest PassageReady. I suggested PassageDone.

    Unfortunately, you are asking for something that can only be done via terrible hacks like the one you described in your original post. But you can easily set up the macro so that it triggers the code in PassageDone from whatever passage(s) you stick it in. There are multiple ways to do it: for example, set a flag/variable in the macro that PassageDone will catch, process, and then unset.
    :: My Macro [widget]
    <<widget "super">>
    <<set $super to args[0]>>
    <</widget>>

    :: PassageDone
    <<if $super neq "">>
    [do whatever with argument stored in the $super variable]
    <<set $super to "">>
    <</if>>
  • CoraBlue wrote:
    I can't add if checks to PassageReady for every passage.


    The general idea Erik put forth is the correct way to go about this.  Why don't you explain in a little more detail what you're trying to accomplish.  There very well may be a way to use the general idea, but without needing to gate for every passage.

    CoraBlue wrote:
    That doesn't even take into account the fact that PassageReady doesn't even work for what you're describing. Try this in PassageReady or PassageDone:
    <<script>>
    alert("Pausing javascript");
    <<endscript>>
    Your alert is going to appear before the passage loads. Your passage will be blank when you see it every time.


    You are mistaken about PassageDone.  By the time PassageDone executes, the contents of the passage have already been rendered and injected into the page.  What you're seeing is that the call to alert() blocks (i.e. the browser tab in question is frozen until you close the alert modal).  Since the tab is blocked, it can't update to show that your content is actually there, but it is.
  • CoraBlue wrote:
    I've been getting around it by wrapping the macro inside a setTimeout(function(){},0).


    I would suggest never doing that, it's buggy for two reasons:
    [list type=decimal]
    You're using an element reference which is not guaranteed to exist by the time the timer fires.
    Any delay in the rendering, for whatever reason, after the timer is created could cause the code to fail, since it might fire before the render is complete.

    And to be specific, I'm not saying don't use setTimeout(), I'm saying don't use setTimeout() in an attempt to inject content into an already rendered set of elements by using an old element reference.
  • [quote]Why don't you explain in a little more detail what you're trying to accomplish.

    Absolutely! I appreciate the help. Both of you. Let's talk through it then.

    Here's a snapshot of my cursor macro being executed.

    undefined

    The nature of this execution means I am skeptical about the use of PassageDone for the following reasons.

    1. The macro is being called more than once per passage.
    2. The location of the macro in the passage changes.
    3. The CSS values also change depending on where the macro is called.

    Specifically what is being attempted is to get the id of the element inside the macro is being called and grab CSS information. Here is my code.
    state.active.variables.fontoffset = $("#"+place.id).css('font-size');
    state.active.variables.fontoffset = Number(state.active.variables.fontoffset.replace("px","")/3);
    state.active.variables.handloc = state.active.variables.fontoffset;
    And I do it again later for line-height. What does this do you might ask? Well the cursor needs to be offset depending on the font information. Here's a demonstration of what I mean.

    undefined

    And this code works great! Only if that delay is there though. Otherwise when it looks for the element I specify, it's looking on the wrong page. Yes, whether its done in the macro or on PassageDone. The same thing happens. It will grab it's values from the last page. And if there is no last page, it's just undefined, which then the macro just breaks entirely.

    undefined

    So the problem, really, is TiddlyWiki's place variable. It does not work unless the passage has been displayed. Otherwise $("#"+place.id) breaks, because there are no elements on the DOM.
  • I'm pretty much a neophyte with HTML5 too, but honestly I think this is presenting difficulties because it's a problem that should be handled with CSS, not with javascript. I don't know how you've set up the pointer icon, but if it is somehow a descendant of the selection boxes (or has another simple, well-defined relationship to them), then you can just write CSS rules to offset it properly.

    Just thinking about it offhand, my approach to implementing the pointer would probably be to assign a class to the element that is being pointed to, and then use a :before CSS pseudo-element on that class to display the pointer. On its own, this might do away with the offset problem. If not, you can set offsets in percentages, or just set different offsets altogether by incorporating the containing element into your CSS rules as a  (e.g.  two rules with different offsets, ".big-box .pointer" vs ".little-box .pointer"). To move the arrow just toggle the class from one element to another; jquery has a number of methods to make this quick and easy. Example in case the basic setup isn't clear: http://jsfiddle.net/2ZqN6/
  • Thanks Erik! It didn't solve the problem yet, but I appreciate the different approach. If it does work it would certainly clean up the code a bit. So I went ahead and combined a section of my code with yours.

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

    (You can use the arrow keys)

    Theres an issue though. You can't use % because the position property is set to absolute. You can't move the position property to relative because then you get a big gap where the hand cursor goes. Any ideas?
  • Yeah, things have to be done a bit differently when you're using an image. Two different ways to handle the specific issues you mention:

    Eliminate the gap by moving the affected element back into place
    http://jsfiddle.net/2ZqN6/7/

    Use a background image and adjust offset using the background-position property
    http://jsfiddle.net/2ZqN6/6/

    But also, you can just set different offsets for different boxes: http://jsfiddle.net/2ZqN6/8/ (see the two CSS rules at the bottom)
Sign In or Register to comment.