Backbone model state when tied to a form
Asked Answered
E

4

10

I'm building a form with Backbone and looking to have it validate its fields on the "blur" event.

Hooking into the event is easy enough, but what I'm curious about is whether or not the model should be updated on blur or only when the form is submitted?

Updating model on blur

  • model.set({...}, {validate:true});
  • if your model has multiple attributes, validation will be run for all of them, every time
  • when creating a new item, the model state isn't as important because it's probably not shared with any other modules yet
  • when editing an item, the model is in this weird outdated/updated state, depending on where the person is in the form. What if the model is being shared between multiple modules?

Updating model on submit

  • can't use model.set() for validation, so the model needs to expose some validation methods (eg MyModel.validZip())
  • on submit, even though all fields have been validated, set() needs to be called to update the model, which will cause validation to happen one more time (not entirely sure this is bad though)

I've read through a couple of relevant Backbone github issues (1, 2, 3) and Backbone devs seem to draw a line between a model and a form.

Additionally, the Backbone.Form plugin appears to keep an internal fields property to track the form fields and when done, call .commit() to update the model.

So it seems like updating the model on submit is the better approach. Is that the experience you've had?

Euphorbiaceous answered 17/12, 2014 at 0:11 Comment(3)
check this one github.com/thedersen/backbone.validation it provides api like model.prevalidate where you can validate the inputs before changing the modelChambertin
Thanks aktiv-coder. Looks like backbone.validation overrides Backbone's validate() method so that individual attributes can be validated at any time. On blur, it's updating the model.Euphorbiaceous
What are your requirements? Should the blur event validate and mark the element as invalid and maybe show an info next to it? Or should this user feedback only be made before submission on server response?Louie
M
6

Proper UX in forms is tricky. I have tried both of your approaches in the past. In my opinion, there is a third option: always keep a model in-sync with your view's state.

This short video highlights some of my reasoning: http://screencast.com/t/qukIe6XW5.

In this video, I am typing into a form. The form auto-updates to show how many characters I have typed and how many are allowed. It's good UX to to provide instantaneous feedback rather than making the user remove focus from the form, find out they have validation errors, and then come back to the form. In order to achieve this, you'll need to be able to know the state of your view at all times.

But I don't want my model's state to be updated automatically! The user needs to press submit first!

It sounds like introducing another class of models would make your life a lot easier. Consider creating a viewmodel which keeps a reference to the instance of your model. With the introduction of a viewmodel you will be able to record the changes to your view without affecting the state of your model. This helps mitigate the risk of bad things happening and, if anything does go awry, your server will be able to catch the changes and fail with server-side validation.

Here are some links to my source for doing in-place editing:

You'll see that I give EditPlaylistPromptView an EditPlaylistPrompt model which stores a reference to the playlist being edited. However, it doesn't directly modify that playlist while the user is working on the view. It just updates the playlist once it passes validation. Note that I'm not going through the model for validation, but I could be, just slacking on that aspect of it.

Here's a picture of how I like to visualize things. It is completely true that a model should be concerned about a view to enforce separation of concerns. However, that's only when you're working with 3 layers of objects. Be more flexible, introduce a new, intermediary layer called a ViewModel and your life gets simpler:

enter image description here

Morelos answered 22/12, 2014 at 21:19 Comment(2)
Thank you Sean for taking the time to respond. Making a copy of the model and syncing it back is a good idea. I guess I was treating the model too autonomously. In regards to a View, it seems the model is fairly tightly coupled, but that is ok because it should not only contain data, but state information (e.g. "hideOptions").Euphorbiaceous
You're very welcome! You don't necessarily need to make a copy of the model. Your 'editing' model could keep a reference to the original. Other properties on your editing model would represent your view's state and, upon submit, would be copied into the held reference. And yes, there's a bit of a difference between a Model and a 'ViewModel.' A Model should be unaware of the view and can be given to multiple views, but a ViewModel has a 1:1 relationship with a given View and helps track state instead of reading/writing from the DOM. :) Let me know if you have more questions!Morelos
W
4

Updating the model on submit seems like a better approach. Backbone is an MVC framework (more of an MV*, but designed to follow the same principles). That being said, the whole point of controllers is to have a separate layer dedicated to restricting, or controlling, how a user interacts with the data (model). So only letting the user update the model until everything is valid conforms with traditional MVC logic.

Additionally, you're only validating once the user has submitted and essentially said 'this is what I want things to be'. Updating on blur could preemptively validate information.

See wikipedia MVC: "[MVC] divides a given software application into three interconnected parts so as to separate internal representations of information from the ways that information is presented to or accepted from the user"

Wofford answered 22/12, 2014 at 21:1 Comment(2)
Thanks Keenan. This is what I was leaning towards, but the missing detail, which other responses mentioned is the View using a copy of the model so that it can handle form state (like validation on blur) without broadcasting changes to other modules.Euphorbiaceous
Agreed, sounds like a perfect case for a view model - "a model level entity that stores UI state"Wofford
B
3

It depends on how you want users to interact with your app. If you want things to always be in sync (like google docs for instance) you could update the model on 'change' then submit to the back-end on 'blur' or with some specific time interval.

If you want users to have to click submit, it would be better not to update the model until you're ready to send data to the back-end.

Bethink answered 22/12, 2014 at 21:20 Comment(0)
E
3

I can only tell from my experience and this is obviously going to be very opinionated.

I don't like to actually set values on a model without asking the server first whether a value change is valid enough to get committed and therefore broadcast to all modules eventually displaying or depending on the model object in question. It is very much possible that an input is not going to be accepted by the server even though it passed all the conditions the client was able to check against (e.g. valid email, but not unique on the server). That being said, some of the input should and can be immediately validated without asking the backend first. In the end, it's probably going to be a "best of both worlds". Shitty wisdom, I know ;).

The most important thing here is the ability to roll back to a previous valid state, no matter what part of the process caused the invalidation. I'm referring to this weird outdated/updated state you mentioned above. We only can absolutely be sure about the correctness of a change if "computer says yes" (backend, that is).

Now, how to achieve that? Here's two approaches (prerequisite for both examples: all model objects have a unique ID attribute and if not we're talking "create"):

Modify a temporary object, mirror to actual object if it validates.

  • Let the form represent a new model object, no matter whether we're talking "update" or "create". You're perfectly able to distinguish between the two by asking for the ID later on:

    // update
    var tempModel = new MyModel(modelToUpdate.serialize());
    // or create
    var tempModel = new MyModel();
    
  • set and / or submit the object values depending on the user experience you want to provide. I would have the validation process as general as possible and always validating the object (each and every key/value pair) as a whole. More on that a little later.

  • In case of a valid (and complete) modification process, mirror the changes back to your actual model object or create a new one:

    var model;
    if (tempModel.isValid() === false) {
        // report Error
    } else if (tempModel.get('id')) {
        model = modelToUpdate.set(tempModel.serialize()).save();
    } else {
        model = tempModel.save();
        // do what you got to do with a new model object
    }
    
  • Major cons: As long as you don't track the temporarily created model object, other parts of the app won't be aware of the upcoming changes.

Keep current state in a temporary object, roll back to its state if validation fails.

  • Save the current and hopefully valid state of the model object before modification process starts to take place:

    var currentlyValid = new MyModel(modelToUpdate.serialize());
    
  • set and / or submit the actual object as you see fit. You should again not care to much about specific key/value pairs but make sure that the whole object is valid, The main advantage here is that other parts of the app depending on and / or displaying the object in question, will stay in sync. Main disadvantage: You got to handle the creation of new items separately.

  • Get lost of the temporary object if modifications got validated. If not, roll back to the previously saved state.

    if (modelToUpdate.isValid()) {
        var currentlyValid = null;
        modelToUpdate.save();
    } else {
        modelToUpdate.set(currentlyValid.serialize());
    }
    
  • Major con: I can't think of a streamlined way of creating new objects with this approach. Depends on implementation, I guess.

I for one prefer the first approach. If it is all about syncing other parts of your app that are supposed to display the changes on a model object with the current temp object on blur, one could establish an application model object that has a property which points at the latter and triggers change events whenever the temporary object gets modified:

var app = MyApplication({
    modelThatIsGettingModified: <Reference to the temp object>; 
});

Why don't we use previousAttributes to roll back?

Because every set call that passed client-side validation will modify the return of this model property, no matter whether the whole object got confirmed by the server or not.

A word about validation

Given the fact that the validation method on Backbone models ...

... is left undefined, and you're encouraged to override it with your custom validation logic, if you have any that can be performed in JavaScript ... (source Backbone docs)

... I'd suggest you always validate against all key/value pairs. May cause some overhead, that's for sure, but this will let you unify validation process. I'm very much aware of 1, 2 and 3, but let's be honest, these kind of things will hardly slow down your app as long as you keep the DOM in check. Having a streamlined validation process is much more important here. If these kind of redundancies start to have an impact on the performance of your thing, you probably shouldn't be using Backbone at all IMHO.

Elide answered 22/12, 2014 at 23:37 Comment(1)
Thank you Lukas for taking the time to respond. I also think option 1 is the better approach and is what Sean's earlier response suggested.Euphorbiaceous

© 2022 - 2024 — McMap. All rights reserved.