What is the equivalent of $ModuleNumber for DynamicModule

Intro

The answer is: Yes, if you know what you want and it makes sense.

Let's say we want to get current parent's module number from withing Module, this is the way to go:

Module[{x}, {x, $ModuleNumber -1 }]

For DynamicModule it does NOT make sense in context of initial evaluation but:

  • it makes sense in context of any evaluation triggered from within generated DynamicModuleBox, at the end it is the FrontEnd who owns it:

    DynamicModule[{x}
    , {
        $DynamicModuleNumber                        (* does NOT make sense *)
          , Dynamic @  $DynamicModuleNumber         (* DOES make sense *)
      , Button["run", Print @ $DynamicModuleNumber] (* DOES make sense on click*)
      }
    ]
    
  • one needs to be aware and EXPECT that if you close/open notebook/cdf containing this DynamicModuleBox, it will change.

  • you need to EXPECT that if you copy and paste the parent box/cell, it will change

  • it may change if you edit an output cell the DynamicModuleBox lives in

  • for modularized/nested apps with multiple DynamicModuleBox instances one needs to know what is going on to stay in control. That is, you can always get the module number in Initialization and pass by value to nested elements in the body if they are built from DynamicModules too.

Those limitations enclose a good bunch of use cases anyway so one may wonder why the api to that number is not public/more user friendly.

Example

DynamicModule[{x, y}
, {
    Dynamic @ DynamicModuleNumber[]
  , Dynamic@x
  , Dynamic@y
  , Button["set x", x = DynamicModuleNumber[]]
  , Button["set y", y = DynamicModuleNumber[], Method -> "Queued"]
  }
] (* twice *)

enter image description here

Code

So, let's cook up something with respect to what makes sense. Your answer already does something like this but it is too cumbersome for me.

DynamicModuleNumber::noparent="DynamicMoudelNumber not executed in DynamicModule";
DynamicModuleNumber::nokids="DynamicMoudelNumber can not access any variable of parent DynamicModule";

DynamicModuleNumber[]:= DynamicModuleNumber[FrontEnd`Private`names]
DynamicModuleNumber[HoldPattern[FrontEnd`Private`names]]:=(
  Message[DynamicModuleNumber::noparent];$Failed
);
DynamicModuleNumber[{}]:=(Message[DynamicModuleNumber::nokids];$Failed);
DynamicModuleNumber[{Hold[sym_Symbol],___}]:=First @ StringCases[
  SymbolName[Unevaluated[sym]]
, __~~"$$"~~dmn:DigitCharacter..~~EndOfString:>ToExpression[dmn]
]

Explanation

When FE sends a packet to evaluation, which comes from an object (Dynamic/Button) embedded in DynamicModuleBox, it wraps the packet with ExecuteInDynamicModule.

E.g. clicking this button

DynamicModule[{},      Button["print", foo[]]    ]

will result in FE --> K

System`EvaluatePacket[ 
  FrontEnd`SynchronousDynamicEvaluate[ 
    FE`ExecuteInDynamicModule[
      {}
    , 89
    , TimeConstrained[
        Function[Global`foo[]][BoxData["\"print\""], Automatic, 1, {}]
      , 6.0
      ]
    ]
  , 0
  , 9.0
  , 383.25714111328125
  ]
]

89 there is the parent's dynamic module number. Fortunately FE`ExecuteInDynamicModule can be PrintDefinitions-ed so we can learn that it does something like:

ExecuteInDynamicModule[init_, serialno_, body_] := Block[
    {names, ...},
    ...
    names = Symbol @ StringJoin[
      "FE`DynamicModuleVariableList$", ToString @ serialno
    ];
    ...
    ... body ...
    ...
];

It means that dynamically scoped (Block) names (FrontEnd`Private`names) will contain information about serialno, which turn out to be a list of variables {Hold[x$$234], ...}.

Great, assuming there is at least one variable in parent DynamicModuleBox we can parse this symbol, which is what the last down value of DynamicModuleNumber does.


Short answer: there is no equivalent of $ModuleNumber for DynamicModule.

Authoritative comments by John Fultz (here and below) about the scoping of DynamicModule and how and why it is differing from the comparably simpler scoping of Module:

[...] "this would require a concept of $ModuleNumber to be handled natively by the FE in the typesetting. Which could not be relied upon in the global namespace, so we'd have to be constantly rewriting the typesetting based upon the global state of the FE. Right now, the only concept of $ModuleNumber is one attached to the kernel instantiation, which does not require typesetting and can be different in different sessions. I'm not saying that would have been impossible to do this, but implementation would have been quite tricksy."

[...] "the case outlined here is equivalent to something like Module[{x}, Function[{y}, Module[{x}, {x, y}]][x]]. Except in typesetting. Module evaluates away on us after doing its replacement, but DynamicModuleBox is a persistent creature, and we must always be able to determine which DynamicModuleBox we want each variable to target in order to solve the problem. You have to do that by renaming the variables in the typesetting. If you're not thinking of this as a typesetting problem, you're not properly understanding the problem."


So, yes, there's no equivalent, but just as a quick work-around we can leverage our knowledge of how the FE works to extract it. Consider this:

DynamicModule[{
  dmlist,
  serialno
  },
 Dynamic@serialno,
 Initialization :>
  (
   serialno =
    Max@
     ToExpression@
       StringTrim[
        Names["FE`DynamicModuleVariableList$*"],
        "FE`DynamicModuleVariableList$"
        ];
   dmlist =
    ToExpression["FE`DynamicModuleVariableList$" <> ToString[serialno]]
   )
 ]

That serialno is now a handle we can use to access the variables the DynamicModule is serializing for us. The dmlist is just a fun example of what it's useful for.

I can't remember why I wanted it, but now it's there.

Just for fun here's how it let's us figure out what a DynamicModule var is called from the boxes:

dynamicModuleSerialNoExtract[box_DynamicModuleBox] :=

  FirstCase[box,
   HoldPattern[_Symbol?(
        Function[Null,
         SymbolName[Unevaluated@#] === "serialno$$",
         HoldAllComplete
         ]) = sn_Integer] :> sn,
   $Failed,
   \[Infinity]
   ];
dynamicModuleSerialNoExtract[e_] :=
  FirstCase[e,
   d_DynamicModuleBox :>
    dynamicModuleSerialNoExtract[d],
   $Failed,
   \[Infinity]
   ];

dynamicModuleVarExtract[varName_String, e_] :=

  Replace[dynamicModuleSerialNoExtract[e],
   i_Integer :> ToExpression@("FE`" <> varName <> "$" <> ToString[i])
   ];
dynamicModuleVarExtract[var_Symbol, e_] :=
  dynamicModuleVarExtract[
   Evaluate@ToString[Unevaluated[var]],
   e
   ];
dynamicModuleVarExtract[a_?(MatchQ[_String | _Symbol]), e_] :=

  dynamicModuleVarExtract[Evaluate@a, e];
dynamicModuleVarExtract~SetAttributes~HoldFirst

It scrapes out this serialno and then sticks it onto the var in question.

Then here's a quick wrapper function for this DynamicModule form:

serializedDynamicModule[{vars___},
   e : Except[_?OptionQ] : Dynamic[serialno],
   ops___?OptionQ] :=
  DynamicModule[{
    serialno,
    vars
    },
   e,
   Initialization :>
    (
     serialno =
       Max@
        ToExpression@
         StringTrim[
          Names["FE`DynamicModuleVariableList$*"],
          "FE`DynamicModuleVariableList$"
          ];
     ),
   ops
   ];
serializedDynamicModule~SetAttributes~HoldAllComplete

And finally here's the thing in action:

NotebookWrite[InputNotebook[],
 Cell[BoxData@ToBoxes@serializedDynamicModule[{}],"Output"]
 ];
dynamicModuleVarExtract[DynamicModuleVariableList,NotebookRead@NextCell[]]

893

{Hold[FE`serialno$$893]}

Note that we really shouldn't be able to extract this, so this is something of a win, even if it's largely without application.