HotDrink / hotdrink

JavaScript MVVM library with support for multi-way dependencies and generic, rich UI behaviors.
http://hotdrink.github.io/hotdrink/
58 stars 9 forks source link

A better textbox #14

Open thejohnfreeman opened 12 years ago

thejohnfreeman commented 12 years ago

We want robust change events:

We want robust mutation operations:

thejohnfreeman commented 12 years ago

Consider this scenario:

At this point, the view element contains a value that does not reflect what is in the view-model. On the one hand, we do not want the view element to always reflect what is in the view-model - the user would never be able to enter an intermediate state that does not pass the validator. That would be frustrating. On the other hand, we probably want the view element to reflect what is in the view-model eventually (to close the gulf of evaluation), so that the user can know what it contains before executing some command that uses the value.

The question is: what time does "eventually" mean? Should it be when the element loses focus? Should it be when the user tries to execute a command that uses the value? Something else entirely?

HeinrichApfelmus commented 12 years ago

The way I see it is that text boxes should be simple for the programmer, which means that he shouldn't have to deal with cursor movement and so on. Setting and retrieving a string value are fine.

For the scenario you mentioned, one of the commenters on my blog (Wolfgang Jeltsch) had the following idea: imagine that the user edits a temporary copy of the model data in the text box.

So, the text box data exists both in the model and in a temporary duplicate.

The advantage of this approach is that, at each moment in time, the displayed text can be given as an equation of values

display_text = if has_focus then temporary_text else model_text

which is a reformulation of the principle 1 I presented on my blog.

thejohnfreeman commented 12 years ago

I think there is motivation for letting an application change a textbox while it has focus, e.g. auto-completion.

Looking at the spec for TodoMVC, I'm trying to deduce the semantic events:

At most one of these events should occur in response to a single user interaction.

Next, what is the data model for the contents? Perhaps it should be a tuple of text (String) and caret position (Number) with these operations:

Lastly, can we all agree on a standard term: caret or cursor? I am in favor of caret because it won't get confused with the mouse and it's the name Java uses, but my opinion is weakly held.

thejohnfreeman commented 12 years ago

Take a look at the BetterTextbox.

gfoust commented 12 years ago

So I've been thinking about the following question: If the user enters invalid data into a widget, and then leaves it and goes on and edits other widgets, what is the expected behavior (especially in regards to variable precedence)? I've tried to answer that question based on why the user left the input in an invalid state:

1.) The user is abandoning the edit; he wants to keep the previous value and discard the edit. The desired behavior here would presumably be to revert the graph to it's state before the edit -- meaning the precedence returns to what it was before the edit. (Which may not necessarily mean staying the same--in the process of editing the input may have passed through other values which were valid and used to update the model.) The only thing we've discussed that would do something like that is the "rollback" feature we had in previous iterations.

2.) The user is planning to return later and "finish" the edit, correcting whatever it was that made it invalid. In this case we would presumably desire that the edited value remain for as long as possible, since the user is planning to come back and finish it. So the variable it is bound to should be given highest precedence (just the same as if it were successfully given a new value) in order that the partial edits be preserved.

3.) The user is planning to make the value valid by editing some other value (i.e. there is a validator which tests the relationship of multiple inputs). Again in this case the edited value should remain for as long as possible, since the user is planning to use it, so the variable should be given highest precedence (just the same as if it were successfully given a new value). Once the value becomes valid, it should be inserted into the model, but it's precedence probably should not change at that point, since that operation is not visible to the user.

So the upshot is this: the desired behavior in cases 2 and 3 is that editing a widget and giving it an invalid value should have the same effect on precedence as editing a widget and giving it a valid value. In other words, precedence simply reflects when a value was edited; not when it was inserted into the model. The desired behavior in case 1 is different, but it cannot be brought about by precedence anyway; it requires an entirely different rollback mechanism, which is orthogonal to the precedence.

Any disagreements? Additional ideas? Comments?

thejohnfreeman commented 12 years ago

So the variable it is bound to should be given highest precedence (just the same as if it were successfully given a new value) in order that the partial edits be preserved.

If the partial edit does not pass the converter/validator, then it won't be in the model. The model is allowed to notify the view with whatever value it has (the last good value sent to it), which will overwrite the partial edit if the view allows it to.

The warning here is that a variable with highest precedence is protected from changes from the system, but views bound to that variable are not (since their changes come from notifications). The motivation for this is a case where multiple views are bound to the same variable: if one of the views changes the variable, all of the views need to be updated.

We can consider changing the system to omit notifying the originator of a variable change, but we need to consider the implications first. This might not be something that needs to be put in the model (that affects all views and binders).

Maybe we need to be asking more questions:

What is the responsibility of a value widget? To dumbly reflect what is in the model, or to provide a temporary workspace to edit what is in the model, or some combination of both? How long is "temporary"?

What is the responsibility of a variable? Should it send notifications only when its value changes (possibly determined by a given comparator), or can it send notifications whenever? Notifications are similar to method invocation. When a method's dependencies change, we guarantee that it will be executed no more than once, but we do not guarantee that it will be executed at least once. We can easily provide the same guarantee for notifications (and maybe should).

gfoust commented 12 years ago

The warning here is that a variable with highest precedence is protected from changes from the system, but views bound to that variable are not (since their changes come from notifications).

OK, I see your point. I guess I was assuming here that the variable does not send notifications unless it is assigned a new value -- either by evaluation of the model or by another view which is bound to it. When such an update occurs, the view will discard the partial edit and replace it with the new value. By giving the variable the highest priority you are guaranteeing that it won't be updated by the model -- at least until some other edit gives other variables higher priority -- thereby helping to preserve the partial edit.

gfoust commented 12 years ago

What is the responsibility of a value widget? To dumbly reflect what is in the model, or to provide a temporary workspace to edit what is in the model, or some combination of both?

I guess I'll take a crack at these. I would say the purpose of a widget is to both reflect what is in the model and to provide a workspace to edit the value. Generally these two tasks are in sync: editing the value in the view updates the value in the model so that the view does in fact represent what's in the model. The only time these two jobs are in conflict are when the edited value will not convert/validate and therefore cannot be inserted into the model. In this case the role of editing takes priority, so the view becomes -- as you said -- a temporary storage location for this partial edit.

How long is "temporary"?

Until the user changes it to a valid value, or the variable it is bound to is updated by some other means. At that point any partial edits are discarded and replaced with the new value.

This "temporary" state -- where the value in the view does not reflect what is in the model -- is obviously less than ideal because we've lost transparency: the value being used by the model is no longer visible in the UI. We have the following options to resolve the situation:

jaakkojarvi commented 12 years ago

John said this a few weeks back:

Undo, perhaps, is a category not covered by the above.