How to add reactor on other sub-model's property change?

31 views
Skip to first unread message

Alexander Gulbit

unread,
Apr 15, 2014, 8:47:48 PM4/15/14
to gl...@googlegroups.com

Hi guys,

I'm playing with reactive concepts while building my simple app.
I have some main_vm with two sub-models session_vm and user_vm.
Once user changes submitLogin prop in main_vm I successfully defined a reactor which fetches session (by calling session.authenticate2() ),
in this fetch session.isValid turns to true, and I want to add to user_vm another contractor to react on this change (with fetching a user from backend),
but I don't know how to do it in a proper way without cheating (and defining some other prop in main_vm like sessionIsValid).


Here is my source faulty code with some comments, and thank you for any feedback:


glu.defModel('myapp.main', {
  modelType: 'main',
   
    session: {mtype: 'myapp.session'},
    user: {mtype: 'myapp.user'},


    isInProgress$: function() { return this.user.isInProgress || this.session.isInProgress },
    submitLogin: false,

    create: function() {
        this.session = this.model('myapp.session');
        this.user = this.model('myapp.user');
        this.user.onSessionValid = {
            on: ['this.parentVM.session.isValidChanged'], // I think the main problem is here in this path, or maybe it something else?
            action: function(){
                if (this.parentVM.session.isValid === true)
                {
                    this.user.id = this.parentVM.session.user_id;
                    this.user.load(function(){},function(){});
                }
            }
        }
    },

    login3: function(){
        //this.submitLogin = true; // why this one doesn't work and I have to invoke setter directly like in the line bellow ?
        this.set('submitLogin', true); 
        
    },

    onSubmitLogin: {
        on: ['submitLoginChanged'],
        action: function(){
            var me = this;
            me.session.authenticate2(me.userName, me.pass,
                function () { 
                    me.session.commit();
                    me.session.set('isValid', me.session.isValid); // in session.authenticate2() I perform me.session.loadData(), but maybe it's not enough,
                                                                                      // so I'm trying to cheat (currently with no result and)  to invoke setter directly?
                },
                function () {me.session.commit();});
        }
    }
}

Mi-e Foame

unread,
Apr 16, 2014, 11:04:48 AM4/16/14
to gl...@googlegroups.com
Hi Alexander! Welcome to the party!

I'm still relatively new to Glu myself, but I do have a couple of things that might be helpful until Ryan stops by. In this piece of code:

    login3: function(){
        //this.submitLogin = true; // why this one doesn't work and I have to invoke setter directly like in the line bellow ?
        this.set('submitLogin', true); 
        
    },

you do need to use "this.set()" so Glu knows to trigger all of the correct events so reactors will work, and also so formulas will be re-evaluated if needed. When simply doing "this.submitLogin = true;" there's no obvious way for Glu to know that something has changed.

I want to add to user_vm another contractor to react on this change (with fetching a user from backend),

My first instinct here would be to use a "me.user.doSomething()" command in one of the callbacks provided to your "me.session.authenticate2()" command. A possible alternative would be to have the the myapp.session viewmodel have its own user property, which would make much more sense to interact with than using "this.parentVM.user" (in my mind).

Again, Ryan will probably chime in with some more helpful advice soon, but I hope this helps until then!

Ryan Smith

unread,
Apr 16, 2014, 11:31:20 AM4/16/14
to gl...@googlegroups.com
Hi Alexander,

First, I'd like to say welcome to GluJS!  Hopefully it will save you as much time as it has for me :).  (Looks like Josh already beat me to some of this stuff, but I'll weigh in with my own 2 cents if its still worth anything :) ).

For your problem, first I'll address the setter vs just setting the property directly.  In javascript there isn't a way to set a listener on a change of a property without having some kind of function call.  In an ideal world, the language would work similarly to something like C# where calling this.property = 'something' would fire a set event.  In the js language, you have to have something to kick the events and so we use the set function to get it all working properly.  The events are all fired and the reactors are all kicked to recalculate and change the UI accordingly.  If javascript ever gets the ability to call a setter by assigning a value to a property, then we can definitely implement GluJS to take direct setters.  But I don't see that happening anytime soon :(.

As for your child-model event handling/linking thing.  My take on these types of interactions is that they are where GluJS really shines, and where you can get some great modularity and separate functionality appropriately to individual models that can be re-used all over the place.  The problem you are having is determining when something in a child has changed from something in the parent though.  This is a little strange for sure and I'll offer up some different ways of accomplishing this:

1.  You can go with mixins.  If you have something like an "Authentication" viewmodel, and a "Main" viewmodel... you could declare on the Main, mixins: ['Authentication'] and every property from authentication will be merged into the Main... making them one and the same.  This can be useful if your only dealing with logic and not the view because all of the authentication logic can be self-contained and fully-tested on its own and then you just have all the properties and functions available to you in the Main viewmodel, so everything works as you would expect it to.  In the past, this has been my preferred mechanism for handling the login functionality for several different pages... but it might not be ideal for your particular situation.

or

2.  You can fire an event from the child that the parent will listen to.  On the init() of the parent you would already have all the models of the children instantiated and ready for you, so you would do something like this from your child model...

authenticate2: function(){
    this.ajax({url: '/path/to/login', params: {}, success: function(){this.fireEvent('loggedIn', this)}})
}

In the Main init you would have something like:
this.session.on('loggedIn', function(){
    //Do stuff when the user is logged in here
}, this)

So rather than having the reactor try to glean what information it needs to listen to, you explicitly fire an event and listen for the event in the parent.  Once the event is fired, you react to it just like you would normally by changing properties and letting the UI respond appropriately.

The reason I like the second approach over using reactors that interpret your intention is that they don't work really well when dealing with parent/child relationships like that because its difficult to know what the intention of that really is.  Also, it keeps things separate so that unit testing can be completely done independently of each other.  You can mock out the authentication viewmodel to only fire the events and make sure that Main does exactly what it should regardless of how the authentication viewmodel might change in the future.  It keeps things very clean and modular (which is what I love to see in MVVM code).

Hopefully that helps you out a bit.  Again, welcome to GluJS... and if you have any other questions, please don't hesitate to ask them!!

-Ryan


--
You received this message because you are subscribed to the Google Groups "GluJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glujs+un...@googlegroups.com.
To post to this group, send email to gl...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/glujs/f68bc928-ae4c-406e-aa24-ba436449fca0%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alexander Gulbit

unread,
Apr 17, 2014, 2:29:18 PM4/17/14
to gl...@googlegroups.com
Thank you for fast answer, and sorry for long delay, I with my family for couple of days.

you do need to use "this.set()" so Glu knows to trigger all of the correct events so reactors will work, and also so formulas will be re-evaluated if needed. When simply doing "this.submitLogin = true;" there's no obvious way for Glu to know that something has changed.

Yep, I get it.
 
I want to add to user_vm another contractor to react on this change (with fetching a user from backend),

My first instinct here would be to use a "me.user.doSomething()" command in one of the callbacks provided to your "me.session.authenticate2()" command. A possible alternative would be to have the the myapp.session viewmodel have its own user property, which would make much more sense to interact with than using "this.parentVM.user" (in my mind).

While playing with my little app I've implemented all of these options you are suggesting here :) In my next answer I will explain the intent of my last version (that I published here in my question)

Thank you once again.

Alexander Gulbit

unread,
Apr 18, 2014, 11:30:02 AM4/18/14
to gl...@googlegroups.com
Thank you for fast and overwhelming answer, and sorry for delay, as I said, I was with my family for couple of days.

 The second option, you've proposed, fits my needs and will work, but... it looks like a trick for me and kind of impurity of using events in Reactive Paradigm. 
So, let me explain my intent.
In RP, where all object's dependencies are written in one formula, it looks like all the context must be kind of  "flat", i.e. all the "cells" that the object depends on must be accessible from the cell (the object) which behavior is described by that formula. From other point of view it creates problem of coupling and redundant referencing in-between objects(cells).
My intent was to solve this problem. And I will try to explain here the way I've chosen to accomplish it.
 Let say you have to children sub-models on main,  let say session and user. When session completes authentication, I want user to get from session some user id and to load itself according to this id.
 Since I don't want to couple Session and User models, I want to facilitate this flow through Main model, the common father, but how?
You propose to put some event handler into father and than to initiate parent's event (in child's callback), and yes this will work, but this is out of RP. 
In my initial intent I was trying to enrich child objects logic while adding reactors within parent c-tor, where fortunately I have all the needed context (or I though that I have :) ) But now after reconsidering all facts, I think I will go on the way where I will define a pare: some state property in Main and reactor for state change. So, in child's callback I will change father's state property (similar to proposed triggerEvent() ) and in Main's reactor will initiate User's load on appropriate state value.

Thank you for help once again.
 
Alexander.

Ryan Smith

unread,
Apr 18, 2014, 11:43:40 AM4/18/14
to gl...@googlegroups.com
I see what you're saying... my suggestion of the firing of the events was really only because that's what GluJS does under the covers for you.  When you change the property in Main for a variable like "foo", there is internally an event fired called "fooChanged" which all formulas and reactors will be listening for.  Basically, you are setting the property and letting GluJS do what I was suggesting to do.  However, you are right... its significantly easier to understand the code in a reactive pattern when you aren't adding your own listeners.  That's what the framework is there for, and so it should be doing all that work for you.  Moreover, if you ever had to have something else listen in on that you'd have to manually add another listener, which would expand your code and create too many points in your code that you have to remember to change.  In your solution, you simply set the property and let GluJS do exactly what it was made for, so that's a great solution.

The only caution for you with this solution is keeping in mind reusability of your children.  If you never will have to use them in a different parent the future (which is what we always say until it changes ;) ) then you're good to go with setting something like this.parentVM.set('userId', this.userId).  But if you have to use it in a different parent, then you'll get runtime errors if you don't have that property.  Its just something to keep in mind as your application grows in size and complexity.  Also, remember to mock out the parent when you are testing the session and user viewmodels separately to actually have that property so that you can test the reactions properly.

Sounds like you have a great handle on the reactive coding paradigm!  If you have any other questions, please don't hesitate to ask.  Also, if you have any suggestions about how to improve the framework, don't be shy about that either.  We're always looking to improve it and make it easier for people to understand.  Have a great day!

-Ryan


--
You received this message because you are subscribed to the Google Groups "GluJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glujs+un...@googlegroups.com.
To post to this group, send email to gl...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages