How to create a notebook element that can replace itself?

This solution relies on putting a TagBox with a custom tag around the part to be replaced, reading the cell and replacing the tag, then writing it back. Personally I've always felt that the need to read the entire cell and write it all again seems kind of clunky, but I don't know of a better way to do this.

MakeBoxes[replacementMarker[a_,tag_],StandardForm]^:=TagBox[MakeBoxes[a],tag]

replaceMark[rule_,nb_:EvaluationNotebook[],which_:All,cell_:EvaluationCell]:=(
SelectionMove[nb,which,cell];
NotebookWrite[EvaluationNotebook[],NotebookRead[EvaluationNotebook[]]/.rule])

Row[{"Not replaced",
replacementMarker[
    Panel[Column[{Style["This is a panel",Bold],Button["Press me!",
    replaceMark[TagBox[_,"replacementTag"] :> MakeBoxes["\" Now I'm just text. \""]]
    ]}]]
,"replacementTag"]
,"Not replaced"}]

Important note! The action cannot be undone, so if used for example to allow dynamic reformatting of data or similar, be aware that mistakes can end up deleting the old contents.


Here's a version of the previous answer that doesn't move the selection and only works at the Box level:

MakeBoxes[replacementMarker[a_, tag_], StandardForm] ^:=

 TagBox[MakeBoxes[a],
  tag,
  BoxID -> tag]

replaceMark[obj_, function_, tag_] :=
 With[{ref =
    FE`BoxReference[
     FE`Evaluate@obj,
     {{tag}}
     ]
   },
  FrontEndExecute@
   FrontEnd`BoxReferenceReplace[ref,
    FrontEndExecute@
      FrontEnd`BoxReferenceRead[ref] // function
    ]
  ]

Row[{"Not replaced",
  replacementMarker[
   Panel[
    Column[{Style["This is a panel", Bold],
      Button["Press me!",
       replaceMark[
        EvaluationNotebook[],
        MakeBoxes[" Now I'm just text."] &,
        "replacementTag"
        ]
       ]
      }]],
   "replacementTag"],
  "Not replaced"}
 ]

It works in exactly the same way, but uses the FrontEnd`BoxReference* functions as "documented" here

The nice thing about this is it makes preserving the TagBox pretty easy. We just need to use a BoxOffset to replace the inner contents:

replaceMark[obj_, function_, tag_] :=
 With[{ref =
    FE`BoxReference[
     FE`Evaluate@obj,
     {{tag}},
     FE`BoxOffset -> {FE`BoxChild[1]}
     ]
   },
  FrontEndExecute@
   FrontEnd`BoxReferenceReplace[ref, 
    FrontEndExecute@FrontEnd`BoxReferenceRead[ref] // function]
  ]

One thing to note when playing with this is that it replaces the next instance of the ID from the current selection, so if you don't delete any replaced cells after your cursor it will seem as if it isn't working. Alternatively provide a UUID to your box:

With[{uuid = CreateUUID[]},
 Row[{"Not replaced",
   replacementMarker[
    Panel[
     Column[{
       Style["This is a panel", Bold],
       Button["Press me!",
        replaceMark[EvaluationNotebook[],
         MakeBoxes[uuid] &,
         uuid]
        ]
       }]
     ],
    uuid],
   "Not replaced"}]
 ]

We can even stick this in the MakeBoxes call:

MakeBoxes[replacementMarker[a_], StandardForm] ^:=

  With[{uuid = CreateUUID[]},
   TagBox[MakeBoxes[a] /. FE`$BoxUUID -> uuid,
    uuid,
    BoxID -> uuid
    ]
   ];
With[{uuid = CreateUUID[]},
 Row[{"Not replaced",
   replacementMarker[
    Panel[
     Column[{
       Style["This is a panel", Bold],
       Button["Press me!",
        replaceMark[
         EvaluationNotebook[],
         MakeBoxes[FE`$BoxUUID] &,
         FE`$BoxUUID
         ]
        ]
       }]
     ]],
   "Not replaced"}]
 ]

Now any raw replacementMarker will make itself unique with the ID being propagated down via FE`$BoxUUID with the UUID being preserved after replacement.


If you want to click on a structure somewhere and have it get replaced, you could use EvaluationBox:

{
a,
Button[
    Panel@Column[{Style["This is a panel",Bold],"Press me!"}],
    NotebookWrite[EvaluationBox[], "\"Now I'm just a piece of static text.\""],
    Appearance->None
],
b
}

enter image description here

After clicking it looks like:

{a, "Now I'm just a piece of static text.", b}

If you want to click on a button, and have some parent structure get replaced, you could use ParentBox + EvaluationBox:

{
a,
Panel@Column[{
    Style["This is a panel",Bold],
    Button[
        "Press me!",
        NotebookWrite[
            ParentBox@ParentBox@ParentBox@EvaluationBox[],
            "\"Now I'm just a piece of static text.\""
        ]
    ]
}],
b
}

enter image description here

After clicking:

{a, "Now I'm just a piece of static text.", b}