component / reactive

Tiny reactive template engine

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2 way binding?

timoxley opened this issue · comments

Angular has this "2 way binding" feature where if you change the value of the thing that is bound, then the object it was bound from is also updated, might warrant another "cooperating" component. How would you go about implementing this?

possibly, I find them pretty leaky personally, but so are other parts of reactive so far, we end up doing quite a bit manually still. It might not hurt to get some of the more common stuff nailed (opt-in of course). I can definitely see a lot of flavours here, might be nice to add the regular old reactive.use(pluginFunction) to facilitate adding these sort of things in bundles.

The first iteration of reactive would have been nicer for two-way since you could do <input name="agree"> and call it a day, two-way bindings right there, but now you would have to do something like <input name="agree" data-checked="agree" data-bind="agree"> hahaha.. pretty redundant, but I'm pretty tired of things trying to be too smart personally, it gets annoying. Even the extra attr beats doing the binding / method yourself all the time for simple cases

You could have the data-bind prefix specify two-way bindings instead

<input name="agree" data-bind-checked="agree" >

@ilsken yeah +1, that would be a nice starting point at least

i think the api only gets leaky if you start dealing with the server. what about keeping the synchronization on the client-side and allow devs to attach onto the change events emitted from the models to deal with persistence / validation / etc.

is the additional binding attribute necessary? if the syncing is going 1-way, cant we just use the same attribute to go the other way?

update: the computed properties would be tricky (how do you reliably update first / last name with fullname?)

I just haven't seen many cases other than little demos where you'd actually gain from it, I can imagine it would get pretty complicated in cases where inputs have any kind of relationship or validation, but we always have the on-input="dostuff" sort of bindings too. Not sure over all, worth playing around with

I think computed bindings should be left to the layer above reactive or the model (having it emit the proper changed event for the computer property)

I think opt-in two-way bindings as an attribute is the right approach, basically what #57 does but perhaps with a better attribute name. I like bind="...".

I think computed properties is great if it works everywhere. Right now it's kinda confusing that it works with data-bindings but not interpolation.

Adding it to interpolation would be kinda tricky right now because it currently evaluates it as a less than sign

we're moving to .computed() instead of the less-than sign, so we won't have that problem in the future.

in my opinion the model would never need to know about computed properties that only the view should be aware of, like fullname.

i think fullname is a bad example actually, since it sounds like something that's in our models (eg. we dont separate into first and last on segment.io), so it's easy for it to "sound" right for the model to be emitting change fullname. whereas properties like empty, hasSettings, onlineNow, or livesNearby or other things that might be computed instead more obviously don't belong in the model, because emitting change has settings sounds ridiculous.

i'm more sold on having computed stuff in reactive than 2-way bindings, because i tend to agree with TJ that it's only useful in super simple cases.

I'd say two-way binding aren't really necessary (maybe something that syncs your forms to the model on submit) as well. I just feel like the .computed function adds something to reactive that could easily be handled by a layer above it since it's all events. You're right about the model but perhaps it should be something handled in the view? The view layer could have access to the model and listen for the changes and emit the proper events for computed properties.

var user = new User({first_name: 'Chris', last_name: 'Tarq', })
var userView = new UserView(user)

reactive(elem, user, userView) // userView can handle computed properties etc

the view layer would definitely have access to the model, but the point of computed properties as i see them is to add sugar so that we don't need to write the tedious event binding logic every time, since computed properties are a pretty common use case

Definetly could use some sugar but is that something reactive should provide? I personally love how reactive makes no assumptions about the model so I have a lot of control over it. I was thinking this would be more of a problem for a component/view component

var View = require('component-view')
var user = new User({first_name: 'Chris', last_name: 'Tarq', })
var userView = new View(user).computed(/* etc */)

Where the sugar is controlled by the view layer. Personally I think reactive's scope should be limited to listening for changes and binding them to the DOM

We should probably move this conversation here though

having it be controlled by a view is interesting. i have toyed with the idea of creating a view factory similar to component/model since i get tired of writing the same constructors all the time:

var reactive = require('view-reactive')
  , template = require('./index.html');

var TileView = view()
  .use(reactive)
  .template(template)
  .computed('fullname', 'firstname', 'lastname');

my spec for views it always just that they take a model and that this.el be an Element (not jquery or component/dom)

reactive might even just be assumed, since even if you're not interested in the change bindings its still a nice way to populate the first time.

Interesting, I have a similar method that I'm using. Although I've gone with having a controller (more of a ViewController) object that can prototypically inherit from the model object (or any object really).

Keep in mind in my model/controller components it uses Object.defineProperty getter/setters to keep track of changes instead of creating functions like component/model

I find this sort of setup works well because each layer is only responsible for a few things. Though ideally the .filter function would be implemented in reactive or a higher level view component which would inherit from reactive. That way you could define global filters or view-level filters much like you can do with bindings.

What do you think? I'd like to see some sort of standard set of higher level libraries like this built around reactive and slowly start replacing angular in my projects.

var reactive = require('reactive')
    , model = require('model')
    , controller = require('controller')

var User = model('User')
    .attr('id')
    .attr('first_name')
    .attr('last_name')
    .attr('created_at')

var UserController = controller()
    // Listens for 'change first_name' or 'change last_name' and triggers 'change full_name'
    .computed('full_name', 'first_name last_name')
    // Defines the full_name as a property
    // Usage is .property(name, getter, [setter]) or .property(name, propertyDescriptor)
    .property('full_name', function(){
        return [this.first_name, this.last_name].join(' ')
    })
    // Same as model.attr() but for the controller
    .attr('foo', {default: 'bar'})
    // Allows you to filter any property/attribute before it's returned
    .filter(function(name, value){
        if(value instanceof Date)
            return value.toLocaleDateString()
                return value
    })

UserController.prototype.login = function(){
    // show the login dialog
}

var user = User({'first_name': 'Chris', 'last_name': 'Tarq', 'created_at': Date.now()})

// userController will be an object that inherits from UserController -> user
// So all the methods from your UserController prototype will be available
// And anything not defined by UserController will resolve to the user object
// This also means they will share an event emitter so change events will work
var userController = UserController(user)

var userView = reactive(document.getElementById('user'), userController)

IMO this is better accomplished with custom bindings you write yourself. I have done so for things like form inputs and contenteditable elements.

cool, np.