Skip navigation
Currently Being Moderated

JS/ScriptUI: placeholder madness

Feb 17, 2012 12:40 PM

Tags: #cs4 #javascript #extendscript #scriptui

I created this yummy text box entry placeholder for CS4 on my Mac:

 

Screen Shot 2012-02-17 at 9.28.09 PM.png

 

but for some reason this fails to draw correctly on Windows! If the edit box is active and the cursor is in it, the text is not drawn but the cursor blinks at correct position at the end of that invisible text. Moving the cursor or typing in something makes the text pop back into view. (Sorry, should have thought of making a 'shot of that as well.) A real shame, because on my Mac it works as I meant it to: placeholder text shows when the field is inactive and empty, placeholder disappears when you enter the field and type anything in it. Delete text and leave box makes it appear again.

 

Is this an error in my code (silently corrected on the Mac but showing up on 'the other platform'), or is it an error on the Windows' Side of the world? Or is this just something one should not be messing with using ScriptUI? (A fairly reasonable explanation.) Or might it work correctly on CS5 for both platforms? (In which case I could put in a platform/version check -- "no fancy dialog for you, you Older Version on a Microsoft Platform!").

 

I also have a variant of same code that would underline static text, which also shows up correctly on Mac and not-at-all on Windows -- but then it may be failing for the very same reason.

 

My code:

 

var w = new Window ("dialog", "Display Placeholder");
w.bg = w.graphics.newBrush(w.graphics.BrushType.SOLID_COLOR,[1,1,1]);
w.textPen = w.graphics.newPen(w.graphics.PenType.SOLID_COLOR,[0.67,0.67,0.67], 1);
with(w.add ("group"))
{
  add ("statictext", undefined, "Text");
  editbox = add ("edittext", undefined, '');
  editbox.characters = 32;
  editbox.onDraw = function(d)
  {
    if( this.text || this.active)
      this.parent.onDraw(d)
    else
    {
      this.graphics.rectPath (0,0,this.size.width,this.size.height);
      this.graphics.fillPath(w.bg);
      this.graphics.drawString("<required>",w.textPen,0,0,this.graphics.font);
    }
  };
}
 
Replies
  • Currently Being Moderated
    Feb 18, 2012 5:48 AM   in reply to [Jongware]

    In CS5.5, your script breaks on this.parent.onDraw(d). The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback. So the question is not "Why does it not work on Windows?", but rather, "Why does it work on a Mac?". When I take out that line the script shows exactly the behaviour you describe. Looks like a bug to me.

     

    On Windows there are some other display problems with edittext controls, but their solution, using this.layout.layout(), wouldn't work in your script. Can you not do something with editbox.onActivate and editbox.onDeactivate? After all, you want the contents to change when the user activates another control, not every time the mouse cursor moves away from the edit field.

     

    Peter

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 7:48 AM   in reply to Peter Kahrel

    The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback.

    What? How is a callback not a function?

    I would think you ought to check that the callback is defined (it might be null or undefined) before trying to call it, but I don't see why it shouldn't be callable.

     

    Jongware: You are also missing a semicolon from that line...

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 18, 2012 7:57 AM   in reply to John Hawkinson

    onDraw is a function but not one that you call like x.onDraw(). But you're right, that error message isn't precise.

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 8:09 AM   in reply to Peter Kahrel

    I'm sorry, you're confusing me further. Why do you think you can't call x.onDraw() if it happens to be defined?

     

    I'm not sure why it would make sense to do so, but you certainly should be able to!

    In this case, neither the Group nor its parent the Window has an onDraw callback defined, so it's not surprising that you cannot call it.

     

    I wonder if Jongware has more code he isn't share with us, such as defining Object.protoype.onDraw or something weird like that...

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 8:16 AM   in reply to John Hawkinson

    For instance, if one adds

     

    w.children[0].onDraw = function() { $.writeln(4); } ;

     

    at the end, then the callback is called just fine. It's not desirable to have a whole lot of 4's in your console, but it's what you asked for...

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 8:23 AM   in reply to John Hawkinson

    Anyhow, Jongware, instead of trying [and failing] to call the parent's onDraw method, you can just:


    editbox.onDraw = undefined;

    and that appears to "work."

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 18, 2012 8:59 AM   in reply to John Hawkinson

    Did I ask for 4s? I mentioned that there seemed to a problem with onDraw(), that is the parentheses following onDraw.

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 10:58 AM   in reply to Peter Kahrel

    Peter:

    Did I ask for 4s? I mentioned that there seemed to a problem with onDraw(), that is the parentheses following onDraw.

    4's are just an example. The point I am trying to make is that you absolutely can call the onDraw handler, exactly as Jongware did in this posted code, as long as such a handler exists. (It appears that perhaps it exists in CS4 if you don't define it, and that behavior changed in CS5).

     

    That is, I took issue with::

    onDraw is a function but not one that you call like x.onDraw(). But you're right, that error message isn't precise.

    onDraw is indeed a function (as long as it exists), and you can call it, and the error message seems fine.

     

    As long as his code uses:

     

    if (this.parent && this.parent.onDraw) {
      this.parent.onDraw(d);
    }
    

    then it is safe.

     

    (you could also write it as this.parent && this.parent.onDraw && this.parent.onDraw(x); if you felt like it.)

     

    You said that you cannot call a callback directly, and I am saying that's not the case. You certainly can. Though you have to make sure it exists.

     

    His oriignal will fail in CS5. It fails not because you cannot call a callback, but rather because you cannot call a nonexistent callback. If you add to the end of the script:

     

    w.children[0].onDraw = function() { $.writeln(4); } ;
    

     

    then there is now a callback for the Group, and then it can be called by the edittext's onDraw method.

    Unfortunately that's not a useful thing to do, because it doesn't cause the Group to redraw. There doesn't seem to be a way to do that, presumably

    because in CS5, the code was restructured internally so you couldn't call the internal drawing mechanism by invoking the onDraw handler.

     

    So a different approach is required. One is to just disable the onDraw handler (set it to undefined), and then the next cycle later, the internal method will do its own thing. Another might be to call .notify("onDraw") on the parent, though I could not get that to work.

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Feb 18, 2012 11:32 AM   in reply to [Jongware]
          this.onDraw(d);
    

    which appears to work as well, but I noticed this sometimes leads to 'double drawing'.

    (2) isn't it downright creepy it appears to be safe to "call itself" again?

    It gives me a stack overflow when I test it.

     

    which looks wonderful on my Mac but it has kind of the same symptoms on Windows: the line is there but the text is not. Now surely that final 'drawString' call ought to have it drawn?

    I've never been able to get drawString() to work properly, but I always felt like I was probably doing something wrong.

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 18, 2012 10:34 PM   in reply to [Jongware]

    Hi Guys

     

    Peter, I'm afraid I should disagree with you on a few points.

     

    (...) In CS5.5, your script breaks on this.parent.onDraw(d). The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback. (...)

    The reason why this.parent.onDraw is not a function in Jongware's code, is just that this.parent.onDraw is undefined—as John supposed. When we call x( ) from an undefined x var, the interpreter prompts a not-a-function error.

     

    Why is this.parent.onDraw undefined? Because there is no line in the code which sets up such member on this.parent—which by the way is a (ScriptUI) Group object, considering the context. What we call 'callback' functions are optional custom methods that are supposed to connect to events, usually, but widget prototypes do not provide any default callbacks. myWidget.onDraw, myWidget.onClick, etc., are simply undefined as long as we don't set them.

     

    In addition, the onDraw callback, when defined, does not work the same way that other event callbacks (which are high-level fake event listeners and could generally be developed through an addEventListener() setter providing much more precision). Indeed, onDraw has no actual event counterpart (or, if any, it is not exposed to scripters as 'show', 'focus', 'blur', 'change', 'click', etc., are.) As documented, onDraw is automatically called when a widget needs to be (re)drawn, but this 'when' is highly unpredictable as it depends on the user interactions, the OS, and probably a number of low-level events.

     

    We also know that custom layout—i.e. myContainer.layout.layout(1)—triggers such drawing stages (from containers). Also, there are crazy paths to get a non-container widget redrawn, for example:

     

        wg.visible=false; wg.visible=true;

     

    or sometimes:

     

       wg.size = [wg.size[0],wg.size[1]];

     

    The issue with manually calling  a custom onDraw() method is we generally don't know when it is safe to call it and whether it may conflict with the automatic calls. I found circumstances where manual onDraw() simply fails due to a temporary invalidation of the widget.graphics property (I believe this depends on the object type). This may explain why Peter wrote:

     

    (...) onDraw is a function but not one that you call like x.onDraw()

     

    However, onDraw is indeed a simple (callable) function if the script did it so. What matters is that a custom onDraw() overrides and bypasses, as much as possible, the native drawing stage. So, in many cases, an empty onDraw() callback makes the widget totally undrawn (hidden) although room has been reserved to it during layout. (That's not true for composite or very interactive widgets—such as scrollbar or listbox—which still inherit from low-level painting events, at least partially.)

     

    ANYWAY, in Jongware's script I don't see any reason to make this.onDraw() attempt to call a hypothetical parent onDraw method—which for the time being does not exist—or even to trigger the native drawing stage on the parent. Why? I guess there is a confusion between drawing and layout (?)

     

    What is sure is that the statement  this.parent.onDraw(d) fails. And, since ScriptUI is known for hushing errors up in many event handlers, you get unaccountable side effects in your EditText control.

     

    To me, a valid approach would be to invoke the ScriptUIGraphics.drawOSControl() method when you need to produce the default widget skin before further customization.

     

    The following snippets should provide better results (unfortunately, I can't test this on all systems):

     

    // =====================================
    // EditText placeholder
    // =====================================
    var u,
        w = new Window('dialog', "Display Placeholder"),
        e1 = w.add('edittext'),
        e2 = w.add('edittext'),
        b = w.add('button', u, "OK"),
        // ---
        wgx = w.graphics,
        grayPen = wgx.newPen(wgx.PenType.SOLID_COLOR,[.67,.67,.67], 1);
    
    e1.characters = e2.characters = 32;
    
    e1.onDraw = e2.onDraw = function(/*DrawState*/)
    {
        var gx = this.graphics;
        gx.drawOSControl();
        this.text || this.active || gx.drawString("<required>", grayPen, 0, 0);
    };
    
    w.show();
    

     

    And:

     

    // =====================================
    // Underlined StaticText
    // =====================================
    var u,
        w = new Window('dialog', "Underline Me!"),
        s1 = w.add('statictext', u, "Not Underlined"),
        s2 = w.add('statictext', u, "Underlined"),
        b = w.add('button', u, "OK"),
        // ---
        wgx = w.graphics,
        linePen = wgx.newPen(wgx.PenType.SOLID_COLOR,[0,0,0], 1);
     
    s2.preferredSize[1] += 3;
     
    s2.onDraw = function(/*DrawState*/)
    {
        var gx = this.graphics,
            sz = this.preferredSize,
            y = sz[1]-1;
     
        gx.drawOSControl();
        gx.newPath();
        gx.moveTo(0, y);
        gx.lineTo(sz[0],y);
        gx.strokePath(linePen);
    };
     
    w.show();
    

     

    Hope that helps.

     

    @+

    Marc

     
    |
    Mark as:
  • Currently Being Moderated
    Feb 18, 2012 10:55 PM   in reply to Marc Autret

    Well, you live and learn. Thanks for your exposé, Marc, that cleared up things. And thanks John, for your observations.

     

    Peter

     
    |
    Mark as:
  • John Hawkinson
    5,572 posts
    Jun 25, 2009
    Currently Being Moderated
    Mar 2, 2012 9:28 AM   in reply to [Jongware]

    Marc's code works Or at least, it does with CS4, both on Mac Lion and on Windows 7.

    I tested it (when Marc posted it) under Lion with the CS5.5 and it was fine.

     
    |
    Mark as:

More Like This

  • Retrieving data ...

Bookmarked By (0)

Answers + Points = Status

  • 10 points awarded for Correct Answers
  • 5 points awarded for Helpful Answers
  • 10,000+ points
  • 1,001-10,000 points
  • 501-1,000 points
  • 5-500 points