Stefan Cameron on Forms
Building intelligent forms using Adobe LiveCycle Designer

Tracking Mouse Clicks

I just recently received another comment from Zack. This time, he was wondering about how one would go about tracking mouse clicks on an image field.

I had never attempted to do that so I took it on as a challenge and thought I would share the results in this post.

I knew from the start that XFA alone wasn’t going to be able to handle this simply because (to my knowledge) it doesn’t provide any information as to the position of the mouse pointer when an event occurs. The most logical place I thought would’ve provided the information — the Event Pseudo Model (the xfa.event object available in all XFA events) — didn’t live up to my expectations. Thankfully, XFA at least provides a Click event so that I could know when the image got clicked.

The next logical place to look was in Acrobat’s Scripting Object Model (in the AcroForm Objects). In the Acrobat Document object, I found what I was looking for: the mouseX and mouseY properties which provided the location of the mouse with respect to the document window.

The last thing I needed was information about the dimensions and location (within the Acrobat Document Object’s coordinate space) of the image field and the Acrobat Field object’s rect property would give me just that.

The combination of the XFA Click event, the Acrobat Document object’s mouseX and mouseY properties and the Field object’s rect property was just what I needed to get this to work.

Of course, I soon discovered that I had another problem to figure-out: The behaviour of an image field in a PDF form running in Acrobat is that when clicked, it opens a browse dialog that lets you pick the content for the field. Unfortunately, there isn’t any way to suppress that dialog other than making the image field read-only or by using a static image object but then both alternatives prevent the Click event from firing. So I needed some clever way to capture a mouse click over an image (whether it was a field or a static object) and I decided to use a button with a transparent fill and no border (so it was essentially transparent). Since buttons are fields just like image fields, the mouseX, mouseY and rect properties would still be available for the button and if I sized the button to fit the image and placed it over-top, I would essentially end-up with an HTML <map>.

Download Sample [pdf]

Minimum Requirements: Designer 7.1, Acrobat 7.0.5.

The first challenge was getting an instance of the Acrobat Field object which represents the button using the

event.target.getField

method. That was easily accomplished by using the script I provided on my AcroForm Field Name Generator article.

The next problem to be solved was the fact that the default behaviour, in Acrobat, for a button when it’s clicked is to invert its content area. Since I was trying to hide the button, I needed a way to suppress the inversion so that clicking on the button would give no visual feedback to the user. That way, it would give the impression that the user was actually clicking on some sort of hyperlink on the image itself. That was easily solved by using the highlight property of the Acrobat Field object representing the XFA button in its Enter event (had to be Enter and not Initialize because Initialize is too early in the form’s initialization process for the association between the XFA button and it’s Acrobat Field counterpart to be established):

event.target.getField(ScriptObject.GetFQSOMExp(this)).highlight =
  highlight.n;

The last problem was with respect to calculating the coordinates of the hot spots on the button that would trigger a reaction (in this sample, I was just going to set the value of a text field somewhere else on the form to reflect the area that was clicked). The problem there was that while I had the mouse location and the button’s dimensions all in the same coordinate space (Acrobat Document), the coordinates were specified with (0,0) set to the document’s lower left corner.

While this may not seem like a big deal to some of you, it really messes me up when (0,0) isn’t at the top left corner (with the maximum (x,y) set to the bottom right corner). I guess that’s a result of years of writing code for Windows where an MFC CWnd’s coordinate space places (0,0) at the top left corner. Anyway, after lots of hair pulling, I finally figured-out how to properly calculate the hot spots in this strange — no, alien — coordinate system.

Zack, if you have any other questions, please post a comment.


Posted by Stefan Cameron on August 4th, 2006
Filed under AcroForm Objects,Scripting,Tutorials
Both comments and pings are currently closed.

20 Responses to “Tracking Mouse Clicks”

  1. Zack on August 8th, 2006

    Thanks a ton Stefan,

    This really helps a lot. I expected that the solution would involve going through something other than the normal forms scripting.

    -Zack

  2. Danny on August 8th, 2006

    Stefan,

    Your work is amazing!! I just checked your new work ‘flashing fields’. Its mind blowing. Can we use the same idea to track all required fields before submitting and flash only the required fields which are not filled??

  3. Stefan Cameron on August 9th, 2006

    Danny,

    Thank you very much! I’m glad you appreciate my posts!

    As for the Flashing Fields sample, I believe you could modify it fairly easily to make all required fields flash at once.

    You could probably achieve that functionality by turning the moFlashTimerID and moFlashingField variables into JavaScript Array objects (one entry for each required, non-filled field), removing the (no longer needed) calls to ValidateField on the Validate events of the form fields and writing a little bit of script on the Submit button’s Click event to go looking for all required fields (check that the mandatory property is set to “error”).

  4. Zack on August 11th, 2006

    Stefan,

    Thanks once again for all the help. Think you’re up for another challenging idea? This time I want to have an image field where clicking on different regions of the image actually cause the image itself to change. A good example would be an image of a traffic light. Let’s say by default all three circles are black, but if you click on the bottom circle it turns green. Clicking on it again would cause it to turn black. The middle and top circles would have similar behavior with red and yellow. This is just a simplified example; I’d like to be able to alternate (non rectangular) regions between various colors.

    One idea I had was to have various images that look nearly identical stored in an XML file and then loading the “right” one into the image field based on which region was clicked. So when the user clicked on the bottom circle of the traffic light for example, I’d just load in the image with the bottom light green and the rest black. The problem I’ve found with this is that it seems dynamically changing the href of the image field doesn’t actually change the picture displayed. Also, if I wanted the different circles to cycle through many different colors, or even have combinations of colors showing at once, it would be very tedious to create and store every single possible image in an XML file. (If there turns out to be a way to dynamically change the image displayed I wouldn’t mind though)

    My next idea was to place every single possible image on top of each other before placing the transparent button over all of them. This way I could simply set every image field to hidden except for the correct one. At first I didn’t think this was going to work, because apparently with the static image object you can only hide them, but not set the presence back to visible. It seems you can do this with image fields however. So far this is my only somewhat viable idea.

    An extension of this idea was to make a base image, and then put several images on top of it that were mostly transparent. This way I could drastically cut down on the total number of images I’d have to create. However, after experimenting with overlapping .png’s and .gif’s, I saw a post on the forums in which you stated that transparency is currently unsupported with image fields, but is on the table.

    Regardless, all of these ideas hinge on the ability to track clicks across non-rectangular regions of the image. I’m not sure that using the Math class to determine the regions is feasible, as some of them might have weird shapes. I’m wondering if there’s some type of method for doing an image map similar to HTML?

    Another idea is to just use an SVG image for the entire thing. However, from what I’ve seen you can’t import an SVG image into an image field. I’m thinking there might be a way to still have one on the PDF without using forms objects, but don’t know how much of the SVG added functionality would stay intact.

    A final brute force idea is to just store each and every pixel of the region in a 2D array and each time they click check to see if the mouse point is a hit for that region. Not sure if it’s even possible to convert this pixel data into points on the image field though.

    As you can see I’m pretty open to how this can be done. I really just have no clue which would be the best way to pursue, of if any of this is even possible. If you could point me in a good direction I’d be even more appreciative than I already am. Thanks a ton,

    -Zack

  5. Stefan Cameron on August 14th, 2006

    Zack,

    You’re really pushing the limits of our image support across XFA (Designer) and PDF (Acrobat) — that’s great! I admire your efforts at coming-up with different possible solutions to your problem.

    I’m afraid your best bet for this kind of click tracking is to use multiple read-only (so that the user can’t click on them to pick another image) image fields with specific iterations of one image (e.g. all the different combinations of red, yellow and green lights). Note that you souldn’t use static images because they’re static (unchanging) by nature.

    To make an image field disabled, use the following script in the image field’s Initialize event (depending on the scripting language you’re using):

    $.access = "readOnly" // FormCalc
    this.access = "readOnly"; // JavaScript

    To load an image at run-time (in Acrobat), just use the following script on the transparent button (depending on the scripting language you’re using):

    // FormCalc
    ImageField.value.#image.href = "picture2.jpg" // JavaScript
    ImageField.value.resolveNode("#image").href = "picture2.jpg";

    SVG might have been a solution but while support is planned, I don’t know when it’ll actually get into the product.

    Transparency would certainly cut down on the number image fields you would need (from 8 down to 4) but that won’t be available until an upcoming release (soon).

    As for tacking clicks on a non-rectangular region, neither XFA nor Acrobat offer the concept of an HTML <map> (to my knowledge, anyway). Therefore, your best bet is some wild math! Rectangular regions are the easiest, circular regions aren’t too bad but other regions are more difficult. For the odd shapes (neither rectangular nor circular), you could try breaking them up into multiple smaller triangular regions because hit-testing a triangle is easier than trying to hit-test something that’s not even a normal geometric shape.

  6. Zack on August 15th, 2006

    Stefan,

    I was afraid that was the case. As far as image fields go, is there a way to load them from the XML file by reference? That is, by just providing the filepath and then storing the images separately? The only way I’ve gotten it to work is actually putting the base 64 image data in the XML file. I’m thinking I may need to set up the schema in a special way or something, not too sure.

    Also, in the code you provided:

    ImageField.value.resolveNode(“#image”).href = “picture2.jpg”;

    is “picture2.jpg” just the relative path to an image stored in the same folder as the form?

    Thanks again,

    -Zack

  7. Stefan Cameron on August 16th, 2006

    Zack,

    The value of the href attribute can be either a relative or absolute path to a file on your local drive or on a server somewhere.

    I believe the piece you’re missing is a script which gets the right URL from your XML Data file and sets it on a certain image field’s href property (in JavaScript below):

    this.value.resolveNode("#image").value.href =
      data.form.image1.value

    This script implies your XML Data file looks like this:

    <form>
      <image1>
        picture2.jpg
      </image1>
    &lt/form>

    You could use this script either on a button click (in the mouse tracking case) or on the Initialize event of an image field to have the image loaded when the form is opened and the data is merged into it.

  8. Susan Mortensen on October 5th, 2006

    I have been reading your posts and with your information I have been able to solve several of my problems, and I thank you for that. The one I can’t solve is being able to change the checkbox so it will be black on the first click, RED on the second click and then off on the third click.

    I would appreciate any information you could give me on this problem.

  9. Stefan Cameron on October 7th, 2006

    Susan,

    What I understand from your question is that you’re wanting to make the check mark (“x”) in a check box be black when its value is “neutral” (the first click), red when its value is “on” (second click) and off when its value is “off” (third click).

    Unfortunately, scripting of the check box’s value (check mark) when it’s in its “neutral” state (first click) is somewhat difficult because Acrobat washes-out the color of the check mark and black becomes gray (which is the default color).

    For instance, you could use this FormCalc script on a check box’s Click event to change the check mark’s color depending on the check box’s state:

    if ($.rawValue == 2) then // neutral
      $.font.fill.color.value = "0,0,0" // black
    elseif ($.rawValue == 1) then // on
      $.font.fill.color.value = "255,0,0" // red
    endif

    This would make the check mark red in the “on” state but it would still be gray in the “neutral” state. Maybe you could use “0,0,255” (blue) for the “neutral” state instead of black.

  10. ashish on December 21st, 2006

    How do i can make a checkbox readonly using javascript

  11. Stefan Cameron on January 4th, 2007

    Ashish,

    You can make a check box read-only in JavaScript using the access property:

    MyCheckBox.access = “readOnly”;

  12. Scott Sheck on June 20th, 2007

    Stefan,

    Thanks for this valuable article. I was fearing there was no way to create a clickable event for my image. Why doesn’t Adobe give the image or one the other objects a clickable event, then we wouldn’t have to jump through hoops using an invisible button? Where can we suggest these ideas so Adobe?

    Thanks,
    Scott

  13. Stefan Cameron on June 25th, 2007

    Hi Scott,

    I agree that it would be really useful to have a Click event on an image. The only problem I see with that is the fact that clicking on an image field in Acrobat has the default behavior of opening the Image File Browser dialog to let you choose a different image. I suppose the image field (as well as the image object) would have to be extended in some way to let the user specify whether the Click event should be passed to the form or to the client application (e.g. Acrobat), the default being to pass the event to the client. This way, you could “override”, in a sense, the Click event and do your hit testing there.

    You can submit bug reports as well as feature requests using Adobe’s Feature Request / Bug Report Form.

  14. pam_a on July 19th, 2007

    Hi Stefan,

    First off… thanks in advance for all your hard work and informative postings! You have been a life saver.

    I downloaded your sample pdf and opened it in Adobe Professional 8.1. The textfield1 doesn’t populate when the invisible button is clicked.

    I can open it in Adobe Professional 7.0.7 and the field is populated.

    Can you supply me with the changes to get it to work in 8.1?

    Thanks in advance.
    Pam

  15. Stefan Cameron on July 30th, 2007

    pam_a,

    You’re very welcome. I’m glad you’ve found the information in my blog to be useful!

    Unfortunately, it looks like there’s a problem with accessing the Acrobat Field Object’s “rect” property in Acrobat/Reader 8.1 which is why the form isn’t working properly.

    While I look for an answer, you could always split the button into three separate buttons laid over top the image and then you wouldn’t need all the mouse tracking script. I realize that would only be a workaround but hopefully it’ll let you move forward with your project while I investigate further.

  16. rob on January 20th, 2010

    Hi Stefan,

    Firstly, thank you for the great posts. As a newbie they have been hugely informative to me.

    I have a PDF document that I wish users to create hotspots on. These hotspots (when clicked) would bring up a dialogue containing info for that particular hotspot.

    The data for the each hotspot would be stored in a database against a particular record.

    I now need to create the user hotspot functionality and tie it back to the data record in the db.

    Can you advise of where in the archives I could put together a process like this? (I’m sure this is a run of the mill question and has already been asked a million times).

    Specifically;
    1. How to create the hotspots and retrieve and store the hotspot co-ordinates for the relevant record.

    2. Use a Form (?) to display the associated database record.

    Any help/ direction would be greatly appreciated.

    rob

  17. Stefan Cameron on January 29th, 2010

    rob,

    Unfortunately, XFA doesn’t give you a way to show a modal (popup) dialog when a user clicks on a hotspot. The best you could do is show/hide a subform with the appropriate fields.

    Since you’re getting data from a database and that you can have multiple ODBC data connections in a single form, you could create one data connection for each of the hotspots, provided you only have a small number of them.

    Then, create a subform for each of the connections with appropriately-bound fields and wire the hotspot action to show the correct subform and hide others. When the subform is displayed, execute its data connection in order to get updated data from the database.

    See my tutorials on database connections for more help.

  18. rob on February 24th, 2010

    Hi Stefan

    Apologies for the delay.

    Thanks for your response, will look a little deeper into the subform idea.

    rob

  19. ealif on March 9th, 2010

    is there anyway to track a from in onsubmit ?

  20. Stefan Cameron on March 14th, 2010

    ealif,

    I don’t understand what you’re wanting to do. What do you mean by “tracking a form in onsubmit”?