The Message Hierarchy
How Director routes event messages through primary handlers, sprite behaviors, cast member scripts, frame scripts, and movie scripts - with pass, stopEvent, sendSprite, and sendAllSprites.
The core rule
An event becomes a same-named message offered to script locations in a fixed order. The first location that handles it consumes it; lower locations never see it unless the handler calls pass.
For a sprite-targeted event (e.g. mouseUp on a sprite):
1. primary event handler property (the mouseUpScript, if set)
2. behaviors attached to the sprite (in attachment order, ALL of them)
3. the sprite's cast member script
4. the frame script (frame behavior)
5. movie scriptsFor frame/movie events (enterFrame, exitFrame, idle...), routing starts at the sprite behaviors for sprite-cycle events or at the frame script for frame-level ones, ending at movie scripts. The exact chain varies by message; when a specific event matters, consult that event's entry in the API reference.
Not DOM bubbling
Director dispatch differs from every mainstream event model, and this is where emulators go wrong:
- Within the sprite location, all attached behaviors that define the handler run, in attachment order (not just the first).
stopEvent()from one prevents the rest. - Once a location has handled the message, propagation stops by default. There is no bubbling to "parent" sprites; sprites have no parents. The hierarchy is script-kind layers, not spatial containment.
passexplicitly forwards to the next location and exits the current handler immediately (statements afterpassnever run).- If no location has a matching handler, the message is simply ignored (and default behavior, like typing into an editable field, proceeds).
pass and stopEvent
on keyDown me
if "0123456789" contains the key then
pass -- let the editable field receive the digit
else
beep -- swallow everything else
end if
endon mouseUp me
if pLocked then _movie.stopEvent() -- kill it here; later behaviors and
end -- lower locations see nothingpass/pass(): forward to the next hierarchy location. Required in editable-field key handlers, or typing dies.stopEvent(): stop the current event's propagation. Documented as meaningful in primary event handlers, handlers called from them, and multiple sprite scripts; no effect elsewhere.dontPassEvent: the Director 5-era predecessor ofstopEvent; still parses and appears in the native symbol tables (old code contains it).
Default behavior interacts with the hierarchy: for keys typed at an editable field, the insertion is the default outcome, which handling keyDown without pass suppresses.
Primary event handlers
Global first-chance interceptors, set as properties:
the mouseDownScript = "trackClick" -- handler name (or script text)
the mouseUpScript = ""
the keyDownScript = "hotkeys"
the keyUpScript = ""
the timeoutScript = "onIdleTimeout" -- with the classic timeout mechanismThe named handler runs before everything. Unless it calls stopEvent() (or the legacy dontPassEvent), the message continues into the normal hierarchy; note this default is the OPPOSITE of normal locations (primary handlers pass by default; script locations consume by default).
Explicit messaging APIs
sendSprite
_movie.sendSprite(3, #reset, arg1)
sendSprite(sprite "hud", #reset) -- name/object forms acceptedDelivers #reset to all behaviors of sprite 3; if unhandled there, the message continues down the hierarchy (cast member script, frame script, movie scripts). Returns TRUE if some behavior on the sprite had the handler, FALSE otherwise. Synchronous.
sendAllSprites
_movie.sendAllSprites(#pause)Offers the message to every sprite in the current frame in channel order (each sprite's behavior chain), then the normal continuation. Slower and less targeted; returns FALSE when no sprite behavior handled it.
call and callAncestor
call(#reset, sprite(3).scriptInstanceList[1], arg)
call(#reset, listOfInstances, arg)
callAncestor(#reset, me, arg)call invokes exactly the given instance(s) with no hierarchy continuation; the precision tool. callAncestor invokes the handler on me's ancestor (the super-call).
Direct handler calls
obj.handlerName(args) and handlerName(obj, args) call one object's handler directly (error if missing; use handler(obj, #name) or objectP guards). Movie-script handlers are callable as bare functions from anywhere: myUtility(5).
Dispatch pseudocode
A compatibility-safe dispatcher:
def dispatch(event, target_sprite, args):
for location in hierarchy_for(event, target_sprite):
handlers = handlers_matching(location, event)
if not handlers:
continue
handled = False
for h in handlers: # >1 only at the sprite-behaviors location
ctx = run(h, args) # ctx.passed / ctx.stopped set by pass/stopEvent
handled = True
if ctx.stopped:
return STOPPED
if ctx.passed:
break # leave this location immediately
if handled and not ctx.passed:
return HANDLED
# passed: fall through to next location
return IGNORED # default behavior may proceedThe highest-risk details, worth explicit tests: behavior attachment order; stopEvent in the first of several sprite behaviors; pass from a sprite behavior reaching cast member then frame then movie script; primary handler pass-by-default; sendSprite continuation below the sprite.
Handler name resolution
- Handler names are case-insensitive symbols.
- A movie-script handler with an event name (
on enterFramein a movie script) acts as the movie-level location for that event every frame; a frame script defining it shadows the movie script for that frame (the message stops at the frame location). - Multiple movie scripts: all are searched in cast order; the first defining the handler wins (two movie scripts defining the same handler is an authoring error with deterministic first-wins behavior).
- Custom messages (
sendSprite(#anything)) use the same machinery as builtin events; there is no registry of "valid" events.