Articles Archive
Articles Search
Director Wiki
 

In Search of Working LDMs

October 9, 2003
by Peter Sorenson

I don't believe the statement "that can't be done." This attitude has helped me create workarounds to what are generally accepted program shortcomings, as well as long nights of head-butting from one wall to the next in an attempt to justify my beliefs.

This is offered as substantiation for doggedly pursuing the holy grail of Director: Fully Functional Linked Director Movies, or LDMs.

For those of you unfamiliar with LDMs, they are, at their most basic, protected director movies that are re-imported into a host movie as content. At their most complex, they hold the promise of asymmetrically communicating encapsulated shockwave objects that enable the simple construction of complex projects.

Much work in the area of LDMs has been done by the director community-at-large, to try to bring the promise of this technology to reality. It was the general acceptance of some very basic problems that made me begin to dig for the answers to three of these issues.

In this article I will detail what these three issues are, how I pursued the solution, and what the solution is (for the nonce) for each issue.

For the record, the three problem areas are:
1) unresponsive mouseEnter/mouseWithin/mouseLeave events (EWL)
2) editable text Field members
3) editable text Text members

EWL (EnterWithinLeave) events

The first mistake the community has made is in believing that LDMs don't respond to these events. They do. This can be proven by:
1. Creating a simple rollover movie (static text member changes color on a rollover),
2. Exporting the movie as a .DCR,
3. Importing it back into its' parent movie (setting the appropriate switches for the LDM member), and
4. Running the host movie in authoring mode.

When you "rollover" the text in the LDM, the "parent text" in the host movie visually reflects the intended action by changing color.

This proves that the LDM is responding to the event. Not correctly mind you, but responding none-the-less. It seems that, although the LDM captures the event, the action is graphically visualized within the host movie "stage" arena. Let's assume that this is the case and also accept three things:
A) the stage can communicate with the LDM sprite thru the undocumented (but widely used) "tell sprite"
B) messages can be sent to the stage from the sprite using the "tell the stage" command
C) the LDM responds to EWL events

The next logical step would be to test the communication between the LDM and itself via a recursive call to the stage. In other words, see if the LDM can talk to itself using the host movie as a sounding board.

So we use this oddly constructed piece of code to help us test the premise.

tell the stage to tell Sprite(gLDMNum) to sprite(myNum)....

But first, a few more points:

Point One: The above code, as well as the entire downloadable behavior written for the LDMs, will work correctly in the Director authoring environment because the GLOBAL gLDMNum has not been assigned a value within the authoring movie. When the movie runs in the authoring environment the statement "tell Sprite(gLDMNum) to" is ignored and the script is executed as if those words were not included in the command. This results in the stage of the authoring movie telling the sprite(s) within it's own movie what code to execute (ie. "tell the stage to sprite(myNum)...")

Point Two: You notice the use of a global (gLDMNum) in the code. At this point in time for the LDM to talk to and identify itself in the host movie we use a GLOBAL VARIABLE to hold the sprite channel that the LDM will occupy in the host movie. The author (you) will know the channel that the LDM will occupy and will set its' value while authoring the host movie. In the sample movie, the LDM "on prepareMovie" behavior sets the value of the GLOBAL gLDMNum dynamically, so that the author does not need to identify the sprite channel, by querying the host for for it's own (the LDM's movie name) and then running thru the sprite channels of the host movie until it finds itself and then exits the repeat loop.

Point Three: The host movie needs the LDM to establish the cast that it uses so the author must name the cast of the LDM uniquely. This way the LDM can refer to its' own cast members, and not have the host movie look to the host movie internal cast to execute action using the members of the host movie internal cast, as it would by default. This can be set either in the on beginSprite or the on mouseEnter script by using the code below.

tell the stage to tell Sprite(gLDMNum) to myLDMCastName = castLib(1).name

This sets a local variable to the name of the LDM's internal cast.

Point Four: Set as much information as you can about the member to local variables. Again, this can be accomplished in the on beginSprite or the on mouseEnter script by using the code below. This helps the LDM efficiently process code directed toward it in the recursive code calls. Although not necessary at all times, it seems to be good practice to use the statement "tell the stage to tell Sprite(gLDMNum) to" when setting the value of local variables.

--set the value of the local variable myNum
tell the stage to tell Sprite(gLDMNum) to myNum = the RollOver
--define my member and the cast to which I belong
tell the stage to tell Sprite(gLDMNum) to myMemberNum = sprite(myNum).memberNum
--define my member name using my cast variable and my member number variable
tell the stage to tell Sprite(gLDMNum) to myMember = (member myMemberNum of castLib(myLDMCastName)).name

Point Five (last one Ð phew): After requesting a change be made in the LDM, you MUST tell the LDM to update itself, using the code below. Without doing this, the code will execute correctly, but will not visually reflect the changes to the host movie stage. It will do so in the authoring mode without the forced update, so watch out when writing your own code for the LDM.

--Address the sprite and change it's color to visually reflect the [enter] action
tell the stage to tell Sprite(gLDMNum) to sprite(myNum).member.color = overColor
--tell the sprite to update its' stage by resetting its' stageColor
tell the stage to tell sprite(gLDMNum) to the stageColor = the stageColor

So using all the points above, we construct simple, yet verbose, code to have the LDM do something, by telling the stage of the host movie to tell it (the imported LDM) to perform an action and update itself. The principles must be applied for all EWL actions.

In this way the shockwave object carries with it all the code necessary to perform its own EWL actions, without having to rely on any behaviors in the host movie, except to create a global variable (gLDMNum) and either set its' value to the channel that the LDM occupies, or to "" letting the LDM set it's own channel value.

Editable Field Member events

The editable field member requires that it become the focus of the keyboard while the user is entering text, numbers, or symbols. It usually does this by default, when it is the only editable member on the stage, and reflects its status thru the "flashing cursor".

Not so in LDMs. It seems that the host movie is not made aware of any of the properties of the imported LDM, so the LDM is forced to "present/declare" itself to the (stage of the) host movie.

To maintain all the code within the LDM, we must have each member within the LDM identify itself uniquely by its' properties. I have done this in the "on mouseEnter" action using the code below, setting a local variable to a returned property value.

tell the stage to tell Sprite(gLDMNum) to tempType = sprite(myNum).member.type
--then set a local variable to whether the member is editable or not
case tempType of
    #text, #field:
tell the stage to tell Sprite(gLDMNum) to tempTypeEd = sprite(myNum).member.editable
end case

I'm not sure the next code is entirely necessary when the editable switch of the field member has been set in the LDM, but it's probably safer to declare the editable property of the member again.

--address the sprite's member and change it's editable state to reflect/
 the [enter] action
tell the stage to tell Sprite(gLDMNum) to member(member myMemberNum of/
castLib(myLDMCastName)).editable = 1

Now we do something that works, but is undocumented and I'm not sure how it works or why. The following code tells the stage to set the focus of the keyboard to the editable field member in the LDM. It must be used in the mouseEnter script.

the keyboardFocusSprite = sprite(myNum) of sprite(gLDMNum)

Director accepts this assignment without an error alert box. The sprite of a sprite. Written a more conventional way, using the logic from EWL actions, the assignment of the keyboardFocusSprite doesn't work ("tell the stage to tell Sprite(gLDMNum) to the keyboardFocusSprite = sprite(myNum)"). The focus remains on the member(s) of the stage of the host movie.

The assignment also works if written this way: -

the keyboardFocusSprite = sprite(myNum)

So I am unsure as to whether the second assignment (of sprite(gLDMNum)) is recognized in the code. I know it fires off an error if used in the assignment of anything else, so we stick to the more conventional (and safer) approach.

Three Caveats: First, the cursor does not change into the I-Beam when you enter the editable field. You have to tell it to change on mouseEnter and to revert on mouseLeave. This inaction of the cursor to change makes me think that there is still some focus/communication issue to work out, albeit a minor one as this solution works.

Second, on the "mouseEnter" action, the assigning the focus of the keyboard to the "subSprite" seems to, by default, select the entire contents of the editable field member. You must then specifically select the text you want to affect. This is the only issue, as keyboard entries function normally.

Third (and final), a forced updateStage is not necessary for the editable field member.

The solution to the editable field member is unique to its ilk (editable), and, unfortunately, not complete. The one function that I have not been able to solve within the LDM (encapsulated shockwave object) yet is "tabbing from one editable field to the next. I am able to solve it through capturing keystrokes in the host movie and passing the result to the LDM using a sendsprite structure, but, as of yet, I can find no way to make the imported LDM sprites aware of keyboard actions. I'm sure there must be a way because the editable field member responds correctly to keystrokes when it has been given the focus of the keyboard, so, on some level it (the LDM) is aware of the actions of the keyboard. The parent movie that imports the LDMs must have some way (an array) of keeping track of the types of sprite that are on stage as the project is built so that "tabbing" works. We could probably build our our array of field members to allow tabbing to override Director's internal array. Oh well Ð a problem for another day.

Editable Text Member events

The editable text member is the real test of one's patience, and I'm sure there is a slicker answer to the riddle, but the final answer will require getting the host movie stage to release the focus of the keyboard to the editable members WITHOUT script intervention from the host movie.

Anyway, here's the problem and the solution.

As we found out earlier, in the case of the unresponsive "tab to next editable field" option in the editable field member in the LDM, the host movie seems to capture the keystroke and direct it, by default, to the stage. This makes sense in a hierarchical sense, as you would normally want the stage to act on keyboard entry.

The only way around this process, at this point, is to programmatically capture the keystroke and pass its' value to a function of the sprite of the LDM that is defined as the keyboardFocusSprite. The positive here is that the keyboardFocusSprite is constantly redefined on mouseEnter within the LDM, yet is released back to the host movie when the editable field in the host movie is selected.

So, in the "on prepareMovie" script of the host movie we need to have the following code.

global gLDMNum

on prepareMovie
  gLDMNum = 16
  the keyDownScript = "pressKey"
end


on pressKey
  tell the stage
    tell sprite(gLDMNum)
      sendSprite(the keyboardFocusSprite, #keyDown, the key)
    end tell
  end tell
  pass
end

Let's review what's going on in the prepareMovie script. The global (gLDMNum) is assigned a value which is the sprite channel number that the LDM will occupy in the host movie. The LDMNum will refer to this value to pass commands to itself. The keydownScript (which retains its' value throughout the host movie (unless reset by the keydownScript = "")) is set to an action defined in the movie script "pressKey".

The "pressKey" handler tells the stage of the host movie to tell the LDM (defined by the GLOBAL (gLDMNum) which is the sprite channel of the LDM in the host movie) to communicate with the editable text member in the LDM using the variable "keyboardFocusSprite to target the sprite in the LDM. The keyboardFocusSprite is redefined whenever an editable sprite (member) is entered in the LDM. The sendSprite action sends the keyboardFocusSprite the value of the key that was just pressed by the user on his keyboard so that the "keyDown" handler in that sprite's behavior can process the request.

The last line in the pressKey script "pass" let's the action continue to be passed unless any other handlers within the movie rely on the keyDown action.

That's it for the host movie. Just a simple "key capture" script to overcome the tendency of the host movie to hog the keyboard.

Next we go to the LDM behavior where we look at the "on keyDown" handler.

First we run a check to see whether the member is a text or field member. Simple enough. What follows is a rather heavy script that basically mimics the typing of characters into a text field. Why? Because even if we tell the host movie that the LDM is the focus of the keyboard, and that the text member is editable, keyboard events do not seem to be passed to the editable text member and recorded into the member. For the record here's the script I used.

on keyDown me, whatKey
  case tempType of
    #text:
      case tempTypeEd of
        1:
          if whatKey = VOID then whatKey = the key
          --set a variable to the number of characters in the text box
          charCount =(member(myMember).char.count)
          put whatKey
          --set a variable to the text in the text box
          stringToChange = (member(myMember).text)
          
          
          --************************************************************************
          --**** IF THIS IS THE FIRST TIME SCRIPT IS RUN SINCE WE SELECTED SOME TYPE
          --*************************************************************************
          --here we have to delete the selected text first
          --but we must set a variable to a value first so that we check it later
          --and make sure we kill the selected Text only once
          --this way we don't have to check for whether the user chose the
          --delete key or not
          if deletedText = 0 then
            --if the user actually selected something in the text box
            if charStart <> charEnd then
              --delete all the selected text
              delete member(myMember).char[startWriteChar..charEnd]
              --change the value of the variable so we bypass this later
              deletedText = 1
              --reset the the variable that tracks the next character to change
              startWriteChar = startWriteChar
              
              if whatKey <> BACKSPACE AND whatKey <>""then
                member(myMember).char[startWriteChar].setContentsBefore(whatKey)
                --**member(myMember).char[startWriteChar] = whatKey
                startWriteChar = startWriteChar + 1
                tell the stage to tell sprite(gLDMNum) to the stageColor = the stageColor
                deletedtext = 1
              end if
              
              --if nothing was selected by the user BUT it's the first time thru
            else
              --check to see if the key pressed was the BACKSPACE or DELETE key
              if whatKey = BACKSPACE or whatKey =""then
                --if there is actually something in the text box
                if charCount >0 then
                  --delete the last character in the string
                  delete stringToChange.char[((startWriteChar)-1)..charEnd]
                  --set the member back to the changed string
                  member(myMember).text = stringToChange
                  
                  startWriteChar = startWriteChar - 1
                  deletedText = 1
                  tell the stage to tell sprite(gLDMNum) to the stageColor = the/ stageColor
                end if
              else
                --change the value of the variable so we bypass this later
                deletedText = 1
                member(myMember).char[startWriteChar].setContentsBefore(whatKey)
                --**member(myMember).char[startWriteChar] = whatKey
                startWriteChar = startWriteChar + 1
                tell the stage to tell sprite(gLDMNum) to the stageColor = the stageColor
                
              end if
            end if
            
            --*********************************************************************
            --**** IF THIS IS THE SECOND TIME SCRIPT IS RUN SINCE WE SELECTED SOME TYPE
            --**** OR NO TYPE WAS SELECTED AT ALL
            --*********************************************************************
            
          else
            if whatKey = BACKSPACE or whatKey =""then
              if charCount >0 then
                --delete the character right befor the one I clicked on
                delete stringToChange.char[((startWriteChar)-1)]--
                --set the member's text to the string
                member(myMember).text = stringToChange
                --change the number of the char to change to the next character
                startWriteChar = startWriteChar -1
                
                put "the new startWriteChar is" && startWriteChar
                
                tell the stage to tell sprite(gLDMNum) to the stageColor = the stageColor
              end if
            else
              member(myMember).char[startWriteChar].setContentsBefore(whatKey)
              --**member(myMember).char[startWriteChar] = whatKey
              startWriteChar = startWriteChar + 1
              
            end if
            
            
          end if
          --*********************************************************************************
      end case
    #field:
      pass
  end case
end

That's the solution for the editable Text member. Not as slick as the others, but it works, and it doesn't rely on the exitFrame or stepFrame handler. Both of which impact performance,

The editable Text Member "keyDown" behavior should really be rewritten to either restrict the keys to a certain set of characters or define the key using its ASCII value (numToChar) and define appropriate actions for each (ie: the arrow keys to allow the user to skip over characters and reset the point at which characters are entered into the text member). My goal in this was simply to overcome the perceived shortcoming of the LDM.

I've included a sample movie and a drag and drop behavior for your use, downloadable by selecting the appropriate hyperlink below.

Macintosh SIT file
Windows ZIP file

Well that's it. One step closer to fully functional LDMs. Does this article answer all the issues? No. It even points out ones that this behavior doesn't deal with, such as tabbing between multiple editable members of the LDM/host movie, or multiple LDMs on the host movie stage at the same time, or multiple instances of the same LDM.

In an effort to put a structure to something that Macromedia seems uninterested in, fully functional LDMs (or encapsulated shockwave objects), I've begun to collaborate with another pioneer in LDMs. With his help, and maybe with the help of others in the community, I'll gain ground in the pursuit of the ultimate goal: e-Learning courseware defined and assembled by XML using encapsulated Shockwave objects as Shareable Reuseable Test Objects.

Peter Sorenson is a Multimedia Designer and Programmer at Pfizer. He designs and codes interactive CD projects and Web-based e-Learning initiatives.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.