Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

A better textbox #14

Open
thejohnfreeman opened this Issue · 9 comments

4 participants

@thejohnfreeman

We want robust change events:

  • Should fire while element has focus and contents change
  • Should not fire on any interaction when contents unchanged

We want robust mutation operations:

@thejohnfreeman

Consider this scenario:

  • A view element is bound to a variable in the view-model.
  • A validator lies in between, protecting the view-model from bad values coming out of the view.
  • The user is editing the value in the view. They stop editing while the value is in an intermediate state - the validator does not accept it, so the view-model still contains the value from the last good state.
  • The user leaves the view element and changes focus.

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

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.

  • Whenever the user presses a key, the visible text is propagated to the model (or dropped silently if it's invalid.)
  • In turn, the model propagates the text value to the text box, unless the text box has the focus, then the temporary data shadows the model data.

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

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:

  • Start editing occurs when the input gains focus. The default behavior could be to move the caret to its location when the element last had focus or to select all of the element's contents (like in TodoMVC).

  • Edit occurs when the user presses a key or clicks. It might insert or delete a character, cut or paste a selection, replace selected text, or do nothing. Are there other possible mutations I'm forgetting?

  • Finish editing occurs when the input loses focus. It might also occur (for text <input>s, not <textarea>s) when the user presses "Enter".

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:

  • getCaret :: () -> Number
  • setCaret :: Number -> ()

  • getText :: () -> String

  • setText :: String -> ()
  • insertText :: Number -> String -> ()

    Inserts a string at a position.

  • removeText :: Number -> Number -> ()

    Removes text in a range.

  • spliceText :: Number -> Number -> String -> ()

    Replaces text in a range with a string.

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 thejohnfreeman referenced this issue from a commit in thejohnfreeman/hotdrink
@thejohnfreeman thejohnfreeman Merge branch 'f-binders' into develop
- Composable binders (closes #7)
- Imperative bindings by directly calling binders. Beware, there is no nice
  way to handle recursive bindings this way. (closes #15)
- Most Knockout binders (starts #11)
- Better textbox (starts #14)
- Lays the groundwork for click-to-edit textbox binder (preps #6)
2904601
@thejohnfreeman

Take a look at the BetterTextbox.

@gfoust
Owner

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

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
Owner

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
Owner

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:

  • We can continue to edit until we reach a valid value, at which point it will be inserted into the model and the two tasks will be in sync again.
  • We can edit other inputs such that the value of the variable reflected by the view is updated by the model. At this point the view will receive update notification and will change to reflect the new value.
  • We can perform some sort of rollback operation which will return both the model and the view to the state they were in before editing began.
  • If the reason the validity of the value is affected by other values, we can edit those other values so that the value in the widget becomes valid; at that point the value will be inserted into the model.
  • We can also help the situation by providing some sort of notification that the value in the view is invalid. This will allow the UI to give some sort of visual cue that the value in the view no longer reflects what is in the model.
  • If we found the situation completely unacceptable we could always bind two views to the variable: one to use for editing, and one to reflect the value in the model. Obviously it would make sense to use a read-only widget (e.g. a label) for the view that is only used to reflect what is in the model. There is no corresponding concept of a write-only widget for the view that is only used to edit the value. I think that's probably for the best; such a widget would not be generally useful.
@jaakkojarvi
Owner

John said this a few weeks back:

  • Edit occurs when the user presses a key or clicks. It might insert or delete a character, cut or paste a selection, replace selected text, or do nothing. Are there other possible mutations I'm forgetting?

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.