Can I bind form inputs to models in Backbone.js without manually tracking blur events?
Asked Answered
D

6

62

I have a backbone.js app (www.github.com/juggy/job-board) where I want to bind my form inputs directly to my model (a la Sproutcore).

Is it possible with Backbone.js (or other tools) without actually tracking each blur events on the inputs and updating the model manually? This seems like a lot of glue code.

Thanks,
Julien

Descant answered 2/11, 2010 at 3:0 Comment(0)
J
34

I'm not sure how SC does it but probably they listen for events too.

window.SomeView = Backbone.View.extend({
  events: {
    "change input.content":  "contentChanged"
  },
  initialize: function() {
    _.bindAll(this, 'contentChanged');
    this.inputContent = this.$('input.content');
  },
  contentChanged: function(e) {
    var input = this.inputContent;

    // if you use local storage save 
    this.model.save({content: input.val()});

    // if you send request to server is prob. good idea to set the var and save at the end, in a blur event or in some sync. maintenance timer.
    // this.model.set({content: input.val()});
  }
});
Joslin answered 9/11, 2010 at 10:18 Comment(6)
I ended up doing exactly that. I works great so far. As you said it saves on every change on the input. The errors are then displayed immediatly, which is good and bad (unmodified fields would display errors like can't be blank when creating a record).Descant
1. one can also try the blur event. 2. I've been pondering on this issue, it would be useful to have a "bindings" array has similar to the "events" hash that specify updates between view parts and model attributes with sync-type parameters (at_change, at_blur etc). say similar to bindings: [["div#title", "model.title", "change", "<-"], ["input#description", "model.description", "change", "<->"]] or something like that, it should be pretty easy to implement.Joslin
I think you can use Handlebar.js as a templating engine. It has this kind of bindings.Descant
@Joslin - hi, shouldn't the events read "change input.content": "contentChanged"? Unless I'm missing something.Gospodin
On changed will accommodate more 'html form elements' altogether - moreover 'blur', 'focus', 'onkeyup' - but, if you continue down this path, it's possible for you to just to implement the age-old 'setTimeout' and 'clearTimeout' solution with a delay on whether to trigger the event or not - ESPECIALLY if you have potentially more than one person 'saving' / 'updating' the model at a time... to constantly save on 'onChange' is really a burden on ur server/site. (just sayin')Nonunionism
Kind of random question, but does one really need the _bindAll? Maybe it's something that's changed since this was written, but I think delegateEvents takes care of binding this for you.Throw
E
53

There is an even nicer way to handle this if your model includes lots of properties in it.

SampleView = Backbone.View.extend({
    el: "#formEl",

    events: {
        "change input": "changed",
        "change select": "changed"
    },

    initialize: function () {
        _.bindAll(this, "changed");
    },

    changed:function (evt) {
       var changed = evt.currentTarget;
       var value = $(evt.currentTarget).val();
       var obj = {};
       obj[changed.id] = value;
       this.model.set(obj);
    }
 });

There is a reliance on your input elements having an id the same as the what the name of the property in your model is.

Empery answered 1/3, 2011 at 21:15 Comment(5)
Would var obj = {}[changed.id] = value; work better than: var obj = "{\""+changed.id +"\":\""+value+"\"}"; var objInst = JSON.parse(obj);Woodchuck
You should never rely on something like: "{\""+changed.id +"\":\""+value+"\"}" at least serialize/escape the value string if you must.Anthem
This same idea has been augmented and implemented as a plug-in: lostechies.com/derickbailey/2011/07/24/…Janey
@porcoesphino excuse me? I don't think I participated on this post.Catchup
@JohnCromartie, yeah the post isn't there anymore. It was something like "this is an awful way to implement it" without any explanation why. I'll delete my comment but first: Do you still think it's awful? If so, why?Madore
J
34

I'm not sure how SC does it but probably they listen for events too.

window.SomeView = Backbone.View.extend({
  events: {
    "change input.content":  "contentChanged"
  },
  initialize: function() {
    _.bindAll(this, 'contentChanged');
    this.inputContent = this.$('input.content');
  },
  contentChanged: function(e) {
    var input = this.inputContent;

    // if you use local storage save 
    this.model.save({content: input.val()});

    // if you send request to server is prob. good idea to set the var and save at the end, in a blur event or in some sync. maintenance timer.
    // this.model.set({content: input.val()});
  }
});
Joslin answered 9/11, 2010 at 10:18 Comment(6)
I ended up doing exactly that. I works great so far. As you said it saves on every change on the input. The errors are then displayed immediatly, which is good and bad (unmodified fields would display errors like can't be blank when creating a record).Descant
1. one can also try the blur event. 2. I've been pondering on this issue, it would be useful to have a "bindings" array has similar to the "events" hash that specify updates between view parts and model attributes with sync-type parameters (at_change, at_blur etc). say similar to bindings: [["div#title", "model.title", "change", "<-"], ["input#description", "model.description", "change", "<->"]] or something like that, it should be pretty easy to implement.Joslin
I think you can use Handlebar.js as a templating engine. It has this kind of bindings.Descant
@Joslin - hi, shouldn't the events read "change input.content": "contentChanged"? Unless I'm missing something.Gospodin
On changed will accommodate more 'html form elements' altogether - moreover 'blur', 'focus', 'onkeyup' - but, if you continue down this path, it's possible for you to just to implement the age-old 'setTimeout' and 'clearTimeout' solution with a delay on whether to trigger the event or not - ESPECIALLY if you have potentially more than one person 'saving' / 'updating' the model at a time... to constantly save on 'onChange' is really a burden on ur server/site. (just sayin')Nonunionism
Kind of random question, but does one really need the _bindAll? Maybe it's something that's changed since this was written, but I think delegateEvents takes care of binding this for you.Throw
A
18

I think this is a cleaner (and maybe faster) way to create an object from an input element

changed: function(evt) {
  var target = $(evt.currentTarget),
      data = {};
  data[target.attr('name')] = target.val();
  this.model.set(data);
},

without jquery:

changed: function(evt) {
  var target = evt.currentTarget,
      data = {};
  data[target.name] = target.value;
  this.model.set(data);
},
Anthem answered 24/3, 2011 at 21:52 Comment(1)
You don't need to pass an object to model.set(). In other words, you could use the following: this.model.set(target.name,target.value);Uhlan
L
12

Have you tried Backbone.ModelBinder? It´s a nice tool to do what you need: https://github.com/theironcook/Backbone.ModelBinder

Lavabo answered 29/6, 2012 at 2:58 Comment(0)
M
0

I'm working on corset, a form library for backbone.js inspired by the django forms module, but a little less ambitious in scope. Still working out the kinks, but it'll end up on github when at least semi-stable and functional.

The aim of corset is to have easily subclassed field classes so that you can build complex inputs for more complex use cases (cascading selects, etc). This approach renders each field as a separate view, and the form view is bound to a model and uses change events, blur events or submit events to update the model (configurable, blur is default). Each view has an overrideable getData function that per default maps to the jquery .val() function.

Using sensible defaults and a modelFormFactory function, we use corset (or the subset of it that is actually done yet) for rapid development, define a model using sensible attribute names, use modelFormFactory and you have instant edit UI.

Moonlight answered 7/5, 2011 at 12:50 Comment(2)
I'd like to see this library! Is it available yet?Justicz
Unfortunately it isn't ready for general consumption yet, but we hope to have it up by august (gotta ship the product first before I can take the time to clean it up and generalize it properly)Moonlight
J
0

I created the following technique on my site

class FooView extends MyView

  tag: "div"

  modelBindings:

    "change form input.address" : "address"
    "change form input.name"    : "name"
    "change form input.email"   : "email"

  render: ->

    $(@el).html """
      <form>
        <input class="address"/>
        <input class="name"/>
        <input class="email"/>
      </form>
    """

    super

    @


# Instantiate the view 
view = new FooView
  model: new Backbone.Model

$("body").html(view.el) 

I've detailed the extensions to backbone you need to make on my blog

http://xtargets.com/2011/06/11/binding-model-attributes-to-form-elements-with-backbone-js/

it uses the same declarative style as the events property for binding form elements to model attributes

and here is the actual code implementing the class for you in coffeescript

class MyView extends Backbone.View

  render: ->

    if @model != null
      # Iterate through all bindings
      for selector, field of @modelBindings
        do (selector, field) =>
          console.log "binding #{selector} to #{field}"
          # When the model changes update the form
          # elements
          @model.bind "change:#{field}", (model, val)=>
            console.log "model[#{field}] => #{selector}"
            @$(selector).val(val)

          # When the form changes update the model
          [event, selector...] = selector.split(" ")
          selector = selector.join(" ")
          @$(selector).bind event, (ev)=>
            console.log "form[#{selector}] => #{field}"
            data = {}
            data[field] = @$(ev.target).val()
            @model.set data

          # Set the initial value of the form
          # elements
          @$(selector).val(@model.get(field))

    super

    @

Appologies if you don't like coffeescript. I do. Everybody is different :)

Justicz answered 11/6, 2011 at 21:11 Comment(3)
Posting generated output isn't particularly helpful for CoffeeScript examples of any size - it's ugly and hard to read because the output is intended for an interpreter, not for reading. You'd never write JavaScript that way by hand. For that reason, it baffles me that so many Coffeescript examples do this at the end with the customary "JavaScript - eww!"Sauls
You can't make anyone happy these days. Just post coffeescript raymond complains. Make an edit and include the translation and insin complains. Given that the question was about form binding in backbone my answer was on topic and probably the most idiomatic backbone solution. The question was about jquery and backbone not javascript specifically.Justicz
Backbone is old hat these days. You should be using angularjs if you want super powered bindings.Justicz

© 2022 - 2024 — McMap. All rights reserved.