Ooui icon indicating copy to clipboard operation
Ooui copied to clipboard

Need of an efficient way to update child nodes of a node.

Open mrange opened this issue 6 years ago • 5 comments

Hi.

For some experiments (formlets) I need an efficient way to update the children of a node.

Essentially I iterate over the set of children of a node and generate a new set of child nodes. Some child nodes in the new set might exists in the old set.

I now need a way to update child nodes of the node to the new set.

Clear the children node and adding the nodes seems to kill the focus (which is super important for me). In addition in many situations there minimal changes to the the new node set so it's seems ineffecient to always clear and add.

I had a PR that was rejected that solved the problem for me. No problem but in the current API I can't realize my use-cases I think. I am happy to accept pointers to a better way of doing it.

Basically the code looks like this for me:

var nodes = parent.Children;
var newNodes = ComputeNewSet(nodes);
parent.Children = newNodes; // Don't work obviously

This code should general a minimal set of events that transforms the client child nodes into the new child nodes.

So this is my wish-list. Perhaps I am thinking wrong about how to work with child nodes in Ooui.

mrange avatar May 16 '18 17:05 mrange

This was my PR:

https://github.com/praeclarum/Ooui/pull/154

mrange avatar May 16 '18 17:05 mrange

In case you have an interest in what I am tinkering with

https://github.com/mrange/Ooui.Formlet

git submodule init
git submodule update
cd src
dotnet run
# Open a browser to: http://localhost:8800/formlet

mrange avatar May 16 '18 17:05 mrange

Warning. It's an F# project :)

mrange avatar May 16 '18 17:05 mrange

The purpose is to define reactive forms using relative little code

    let address lbl =
      Formlet.value Address.New
      <*> any       "C/O"
      <*> notEmpty  "Address"
      <*> notEmpty  "Zip"
      <*> notEmpty  "City"
      <*> any       "County"
      <*> any       "Country"
      |> group lbl

    let person =
      Formlet.value Person.New
      <*> notEmpty  "First name"
      <*> notEmpty  "Last name"
      <*> socialNo  "Social no"
      |>> Person
      |> group "Person"

    let company =
      Formlet.value Company.New
      <*> notEmpty  "Company name"
      <*> notEmpty  "Company no"
      |>> Company
      |> group "Company"

    let entity =
      formlet {
        let! legalEntity = select "What legal entity is the registration for?" [|"A person", person; "A company", company|]

        let! invoiceAddress = address "Invoice Address"

        let! useSeparateDeliveryAddress = checkBox "Separate delivery address?"

        let! deliveryAddress =
          if useSeparateDeliveryAddress then
            address "Delivery Address" |>> Some
          else
            Formlet.value None

        return CustomerRegistration.New legalEntity invoiceAddress deliveryAddress
      }

mrange avatar May 16 '18 18:05 mrange

I'm doing something similar in csharp. I build a new element tree by reflecting against the application state after each state change. I then have to replace the ooui forms with the new tree.

I'd like some way to diff this against the previously generated tree, and just send the difference down the wire. I could probably do it by rewriting my reflection code to update the tree, but if Ooui DOMs were diffable I think could keep the code as a simple transform.

Here's my app:

void Main()
{
	var target = new TaskList();

	UI.Port = 8081;

	UI.Publish("/reflected", () => Sync(target));
	new Hyperlinq($"http://localhost:{UI.Port}/reflected").Dump();
	Console.ReadLine();
}

class TaskList
{
	public List<string> Tasks { get; private set; } = new List<string>() { "goo"};
	public void AddTask(string task)
	{
		Tasks.Add(task);
	}
}

Element Sync(object target){
	var panel = new Div();
	
	var element = Reflected(target, Refresh);
			
	return panel;
	
	void Refresh(){
		panel.Children.ToList().ForEach(c => panel.RemoveChild(c));
		panel.AppendChild(Reflected(target, Refresh));
	}
}

Element Reflected(object target, Action refresh)
{
	if (target.GetType().IsGenericType && target.GetType().GetGenericTypeDefinition() == typeof(List<>))
	{
		var listContainer = new Div();
		foreach (var value in ((IEnumerable)target))
		{
			var child = Reflected(value, refresh);
			listContainer.AppendChild(child);
		}
		return listContainer;
	}
	if (target.GetType() == typeof(string))
	{
		return new Div { Text = target as string };
	}
	
	//object
	var container = new Div();
	foreach (var property in target.GetType().GetTypeInfo().GetProperties())
	{
		var propertyContainer = new Div();
		propertyContainer.AppendChild(new Div() { Text = property.Name });
		object value = property.GetValue(target);
		Element element = Reflected(value, refresh);
		propertyContainer.AppendChild(element);
		container.AppendChild(propertyContainer);
	}

	foreach (var method in target.GetType().GetMethods().Where(t => !t.IsSpecialName && t.DeclaringType == target.GetType()))
	{
		var methodContainer = new Div();
		methodContainer.AppendChild(new Div() { Text = method.Name });
		method.GetParameters().ToList().ForEach(parameter =>
		{
			methodContainer.AppendChild(new Div() { Text = parameter.Name });
			var input = new Input(InputType.Text);
			methodContainer.AppendChild(input);
		});

		var button = new Button() { Text = method.Name };
		button.Click += (obj, arg) =>
		{
			var methodArgs = methodContainer.Children.OfType<Input>().Select(i => i.Value).Cast<object>().ToArray();
			method.Invoke(target, methodArgs);
			refresh();
		};
		methodContainer.AppendChild(button);
		container.AppendChild(methodContainer);
	}

	return container;
}

mcintyre321 avatar Jun 04 '18 21:06 mcintyre321