Skip to content

Commit c63ea4e

Browse files
committed
Preserve element ids between renders
1 parent 3f997bf commit c63ea4e

1 file changed

Lines changed: 50 additions & 3 deletions

File tree

Abies/Types.cs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Runtime.CompilerServices;
44
using System.Runtime.InteropServices;
55
using System.Runtime.InteropServices.JavaScript;
6+
using System.Threading;
67
using Abies;
78
using Abies.DOM;
89

@@ -164,6 +165,7 @@ public record Program<TApplication, TArguments, TModel> : Program
164165
private Node? _dom;
165166
// todo: clean up handlers when they are no longer needed
166167
private readonly ConcurrentDictionary<string, Message> _handlers = new();
168+
private readonly SemaphoreSlim _dispatchLock = new(1, 1);
167169

168170
public async Task Run(TArguments arguments)
169171
{
@@ -210,8 +212,44 @@ private void RegisterHandlers(Node node)
210212
}
211213
}
212214

215+
private static Node PreserveIds(Node? oldNode, Node newNode)
216+
{
217+
if (oldNode is Element oldElement && newNode is Element newElement && oldElement.Tag == newElement.Tag)
218+
{
219+
var attrs = new Abies.DOM.Attribute[newElement.Attributes.Length];
220+
for (int i = 0; i < newElement.Attributes.Length; i++)
221+
{
222+
var attr = newElement.Attributes[i];
223+
attrs[i] = attr.Name == "id" ? attr with { Value = oldElement.Id } : attr;
224+
}
225+
226+
var children = new Node[newElement.Children.Length];
227+
for (int i = 0; i < newElement.Children.Length; i++)
228+
{
229+
var oldChild = i < oldElement.Children.Length ? oldElement.Children[i] : null;
230+
children[i] = PreserveIds(oldChild, newElement.Children[i]);
231+
}
232+
233+
return new Element(oldElement.Id, newElement.Tag, attrs, children);
234+
}
235+
else if (newNode is Element newElem)
236+
{
237+
var children = new Node[newElem.Children.Length];
238+
for (int i = 0; i < newElem.Children.Length; i++)
239+
{
240+
children[i] = PreserveIds(null, newElem.Children[i]);
241+
}
242+
return new Element(newElem.Id, newElem.Tag, newElem.Attributes, children);
243+
}
244+
245+
return newNode;
246+
}
247+
213248
public async Task Dispatch(Message message)
214249
{
250+
await _dispatchLock.WaitAsync();
251+
try
252+
{
215253
if (model is null)
216254
{
217255
await Interop.WriteToConsole("Model not initialized");
@@ -232,8 +270,10 @@ public async Task Dispatch(Message message)
232270
// Generate new virtual DOM
233271
var newDocument = TApplication.View(newModel);
234272

273+
var alignedBody = PreserveIds(_dom, newDocument.Body);
274+
235275
// Compute the patches
236-
var patches = Operations.Diff(_dom, newDocument.Body);
276+
var patches = Operations.Diff(_dom, alignedBody);
237277

238278
// Apply patches and (de)register handlers
239279
foreach (var patch in patches)
@@ -257,14 +297,21 @@ public async Task Dispatch(Message message)
257297
}
258298

259299
// Update the current virtual DOM
260-
_dom = newDocument.Body;
300+
_dom = alignedBody;
261301
await Interop.SetTitle(newDocument.Title);
262302

263303
foreach (var command in commands)
264304
{
265305
await HandleCommand(command);
266306
}
267-
} private static async Task HandleCommand(Command command)
307+
}
308+
finally
309+
{
310+
_dispatchLock.Release();
311+
}
312+
}
313+
314+
private static async Task HandleCommand(Command command)
268315
{
269316
switch(command)
270317
{

0 commit comments

Comments
 (0)