Dynamic Binding in Ember.js Using Observers

knot_binding

I’m currently working on an Ember.js web application that is a basic CRUD app with a twist — we don’t know all of the form fields or model attributes ahead of time, but rely on a JSON document to specify the names and types of attributes for which we need to render form controls. This presents some interesting challenges, the most significant being that our input elements don’t know what to bind their value to until they are instantiated.

After trying a few different approaches, we settled on using a pair of observers to accomplish what a more direct binding could not in this scenario. One observer watches the input’s @value@ property and updates the remote property value, and the other observer watches the remote property and updates the input’s @value@.

h2. Our Solution

Read on for a few more details. This example uses Ember.js 1.0.0-RC.1, and we are using the same general solution in 1.0.0-RC.3.

Say we have the following definition for a set of fields:

{"fields":  [
  { "name": "First Name", "key": "first_name" },
  { "name": "Last Name", "key": "last_name" },
  { "name": "Email", "key": "email" }
]}

We want to display a text field for each element in the array and bind its value to a property on our model defined by the @key@. This example uses a @CollectionView@ to render a subclass of @Ember.TextField@ for each element. The @DynamicBoundTextField@ here has the pair of observers.

App.DynamicBoundTextField = Ember.TextField.extend
  placeholderBinding: 'content.name'
  
  # Update the remote property's value when the value of
  # our text field changes
  _setRemoteValue:(->
    val = @get('value')
    @set("controller.data.#{@get('content.key')}", val) if val?
  ).observes('value')
  
  # Since we don't know the name of the property to
  # observe ahead of time, create the observer when
  # inserted into the DOM (we'll have the key then).
  didInsertElement: ->
    updateCurrentValue = =>
      currentValue = @get("controller.data.#{@get('content.key')}")
      console.log currentValue
      @set('value', currentValue) if currentValue?
    updateCurrentValue()
    @addObserver "controller.data.#{@get('content.key')}", updateCurrentValue

Our implementation uses an intermediate object to perform the observation because it requires specialized knowledge about the context that the binding is being performed in, and we felt it was odd having that information on the input itself. But it works the same way.

Try it out in this JS Bin: “Runtime binding using observers JS Bin”:http://jsbin.com/ebusip/3/edit.

h2. Things that Didn’t Work

h3. TextField with a valueBinding

The primary constraint we’re dealing with is not knowing what the TextField should bind to until it’s created. This means we can’t simply place a @valueBinding@ on our text input in our class definition like most Ember examples show. It needs another level of indirection to work.

h3. TextField and an Intermediate Object with Bindings

So we tried creating a @valueBinding@ to a known property, @inputValue@, of an intermediate view. The intermediate view then bound its @inputValue@ to the target property in @init@. This caused interesting but unwanted behavior, like the target property getting set to undefined instead of the input’s value getting set to the existing value of the target property. It just didn’t work out very well.

Our current, working implementation does have an intermediate object, but it uses the observer pair pattern instead of a binding to the target property to avoid the bad behavior described above.
 

 
Conversation
  • biiiipy says:

    I know that it is completely unrelated, and I’m sorry, but I just wanted to say that… well… with AngularJS this would’ve been soooo trivial…

    • Peter Brown says:

      @biiiipy instead of trolling, it would have been awesome if you provided an example in Angular for people who are evaluating frameworks.

      • biiiipy says:

        No problem. Tried to stay as close as possible to your example.
        Here you go – http://jsbin.com/uxasaf/2/edit
        Simplified version without all the templating and routing – http://jsbin.com/uxasaf/4/edit

        As I said – trivial…

      • biiiipy says:

        No problem. Tried to stay as close as possible to your example.
        Here you go – jsbin.com/uxasaf/2/edit
        Simplified version without all the templating and routing – jsbin.com/uxasaf/4/edit

        As I said – trivial…

  • Jason Porritt Jason Porritt says:

    @biiipy: I would love to see an example if you’re willing. We considered Angular for this project but chose Ember based on others in our office having used it on a few projects with great success.

    Ember may not make this trivial, but it makes it possible with a relatively simple pattern. Most frameworks require the application of more robust development patterns to write non-trivial software within their paradigms. So far Ember has required fewer than most.

  • Héctor says:

    Thank you,

    I had the exact same problem, but I’m new to Ember, so I didn’t know how to do it.

  • Dhara Joshi says:

    So helpful!

    Thanks a ton :)

  • Benito says:

    Great! That’s exactly what I was looking for. I’m using a CollectionView, and I didn’t see how I could bind each of the item views’ value to my controller’s values.
    However I have a question: could not the same thing be achived by using manual bindings with Ember.Binding (see http://emberjs.com/api/classes/Ember.Binding.html)?
    Thanks anyway!

  • Comments are closed.