These are chat archives for jdubray/sam

26th
Jan 2017
Jean-Jacques Dubray
@jdubray
Jan 26 2017 01:15
@DrecDroid One key aspect of SAM semantics is the concept of a "step", the only way to implement this kind of behavior is to store the corresponding state in the model. You really don't want to change the definition of a step. That's where the spaghetti code comes from.
For instance in my projects I don't use a vdom library. I don't see the point (in general). So so avoid redrawing the entire page I just define a number of sections 5-10 and keep track of whether the corresponding model properties have changed. Happy to hear counter arguments, but I have used that approach successfully. The alternative would be to use vdom, if nothing changes your vdom library will not mutate the DOM tree.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 03:51
I want to follow your recommendation but I don't know how to accomplish modular components easily without the use of something like React. Right know I'm writing a SAM implementation for my project that is working good but I don't know how close is It to SAM pattern and if It will not cause me headaches in the long term. I've made a gist of what I basically doing https://gist.github.com/DrecDroid/ffd7411437f12f23f7e0aa6377228e19. Basically I've divided the model/store in two parts one that I'm calling modelthat is a javascript Object {} and the other called state that is a javascript Map(). I'll try to upload my lib although is really small but working normally right know.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 05:44
This is the lib, is far from being stable. https://github.com/DrecDroid/tioSAM
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:19
@DrecDroid I have done a number of real-world projects with SAM and I feel that the componentization is different from what more people would think a component boundary should be.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:27

First, in SAM the View Components are 100% decouple from the business logic. View components publish events and wait for state representation updates. They know nothing about what's happening in the SAM loop. With ES6 that's really simple to implement, you don't need JSX or Angular2/Vue templates.

Here is for instance a "real" footer component using a simple ES6 template literals:

footer(params) { 
        params = params.footer || {};
        return (`
        <div class="container">
        <div class="row pb-100 pb-md-40">
          <!-- widget footer-->
          <div class="col-${params.acols || 'md-6'} col-${params.acols2 || 'sm-12'} mb-${params.acols || 'sm-30'}">
            <div class="logo-soc clearfix">
              <div class="footer-logo"><a href="${params.logoHref || 'index.html'}"><img src="${params.logo || 'img/logo-white.png'}" data-at2x="${params.logo2x || 'img/logo-white@2x.png'}" alt></a></div>
            </div>
            <p class="color-${params.color1 || 'g2'} mt-10">${i18n(params.mission)}</p>
            <!-- social-->
            <div class="social-link dark">${ (params.social || []).map(function(item) {
              return `<a href="#" class="cws-social fa fa-${item.network}"></a>` ;
              }).join('\n') 
            }</div>
            <!-- ! social-->
          </div>
          <!-- ! widget footer-->
          <!-- widget footer-->
          <div class="col-${params.bcols || 'md-3'} col-${params.bcols2 || 'sm-6'} mb-${params.bcols3 || 'sm-30'}">
            <div class="widget-footer">
              <h4>${i18n(params.title)}</h4>

              <!-- div class="twitter-footer align-left"></div -->
            </div>
          </div>
          <!-- end widget footer-->
          <!-- widget footer-->
          <div class="col-${params.ccols || 'md-3'} col-${params.ccols2 || 'sm-6'}">
            <div class="widget-footer">
              <h4>${i18n(params.activityTitle || 'tagcloud')}</h4>
              <div class="widget-tags-wrap">
              ${ (params.activities || []).map(function(activity) {
                return `<a href="${activity.href || '#'}" rel="tag" class="tag">${i18n(activity.label || '#')}</a>` ;
                  }).join('\n')
                }
               </div>
            </div>
          </div>
          <!-- end widget footer-->
        </div>
      </div>
      <!-- copyright-->
      <div class="copyright"> 
        <div class="container">
          <div class="row">
            <div class="col-${params.dcols2 || 'sm-6'}">
              <p>${i18n(params.copyright)} &nbsp;&nbsp;|&nbsp;&nbsp; ${i18n('allrightsreserved')}</p>
            </div>
            <div class="col-${params.menuCols || 'sm-6'} text-${params.menuAlign || 'right'}">
            ${(params.menu || []).map(function(item) {
                  return `<a href="${item.href}" class="footer-nav">${i18n(item.label)}</a>` ;
                }).join('\n') 
              }
          </div>
        </div>
      </div>
      <!-- end copyright-->
      <!-- scroll top-->
        `) ;
    },
As you can see, I can take the work of the designer and directly integrate it in the code, no painful translation into anything else than HTML5/CSS3/ES6.
Now on the SAM loop, I have published a simple module architecture which is illustrated here: https://github.com/jdubray/startbootstrap-clean-blog/blob/master/components/menu.component.js
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:34
You can package up the model's units of work and actions into these modules. They don't have to follow the view components at all (and in general they won't). For instance, here is a "search" action:
name: "search",
            implementation: function(data, present, model) {
                data = data || { } ;
                data.__action = 'search' ;


                var $j = jQuery.noConflict() ;

                $j.ajax({
                    type: "post",
                    accepts: "application/json; charset=utf-8",
                    url: host+'/app/v1/search',
                    data: data,
                    success: function(results) {
                        if (typeof results === 'string') {
                            results = JSON.parse(results) ;
                        }
                        data.items = results ;
                        if (model) { 
                            model.present(data) ;
                        } else {
                            present(data) ;
                        }
                    }, 
                    error: function(err) {
                        console.log(err) ;
                    }
                });


                return false ;
            }
SAM's programming model is very clear when it comes to API calls, no need for thunks, sagas or whatever. As you can see, no knowledge of the "view component" which triggered the actions, just an event, and no knowledge of the model's unit of work that would take the search results and mount them in the model.
The corresponding unit of work (model) looks like this:
{ 
            name: "newItems",
            order: 1,
            update: function(model,data) {
             // Accept search results
                if (data.items) {
                    model.data.home.portfolio.items = data.items ;
                    model.update.p = true ;
                }
            }
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:40
Note the model.update.p that provides a hint to the state representation that the "p" section of the view needs to be refreshed.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:46
First, I would never use React. Some people would think that I have a personal issue with the React team, but in all fairness, the answer is clearly no. Even though SAM is inspired by React, React's programming model simply makes no sense, and today React has become a zoo of micro libraries that only exist because no one at Facebook and in the React community is trying to clean up the programming model.
In the code that you shared for instance, the "view components" should never directly consume the model properties nor take the decision to render. That is the role of the State Representation.
After each step all the rendering hints are reset to false, there is no need to do that individually.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 09:53
I like the "acceptor" moniker for the model's units of work. I'll steal it (with credits).
I like the general structure of the tioSAM library. I am a bit puzzled though by this:
state('TASK_LIST_CHANGED')
Jean-Jacques Dubray
@jdubray
Jan 26 2017 10:05
IMHO, several "acceptors" could trigger when processing a proposal, so you should be careful to avoid a strong coupling between action/acceptor/state. They should all be as decoupled as possible
And of course the view component should know nothing at all about SAM, so something like:
return this.props.model.state.TEXT_CHANGED ? true : false;
is strictly forbidden
and not needed
Jean-Jacques Dubray
@jdubray
Jan 26 2017 10:11
I would say, I don't like both options, because it introduces a coupling that's not necessary, but perhaps I don't understand the state(xxx) option.

React is way too reactive to be useful, IMHO. So when you do something like this in the model:

state('TEXT_CHANGED') // this does this.state.set('TEXT_CHANGED', true)

I feel that's simply wrong. You are no way in the position to render anything at this point of the code. That is raw spaghetti (courtesy of React's programming model).

you need to complete the "Step" then you can render.
Fred Daoud
@foxdonut
Jan 26 2017 14:36
@jdubray nice comments, very useful in better understanding the SAM pattern :thumbsup:
@jdubray in your code example, theme.js has <a id="${item.href}" href="#" onclick="return a.setMenuItem({menuItem:'${item.href}'});">${item.label}</a>. Where is it established that the view will have access to the a variable?
Jean-Jacques Dubray
@jdubray
Jan 26 2017 14:42
That's the only "leak" I cannot seem to be able to fix, it's in the index.html file when I bootstrap SAM
There is my typical bootstrap:
<script type="text/javascript">
        var a = {} ;

        initPageLifeCycle() ;

        var language = getParameterByName("lang") || window.navigator.userLanguage || window.navigator.language || 'en'; 

    </script>
Then I initialize SAM and connect a with SAM's actions
<script type="module">

          // standart SAM  
          import {state}       from './sam/state.js' ; 
          import {model}       from './sam/model.js' ;
          import {actions}     from './sam/actions.js' ;
          import {view}        from './sam/view.js' ;

          // application specific
          import {theme}       from './components/theme.js' ;

          // initial model, from file
          // import {data}           from './components/model.data.js'

          // initial model, from server
          import {data}                       from '../app/v1/model.js' 

          // You can also fetch the site's metadata from a static file
          //import {data}                     from './static.model.js'

          import {init, 
                  getParameterByName,
                  preparePage, 
                  checkBackButton}         from './components/init.components.js' ;



          var display = function(representation) {
                preparePage(model) ;
                if (representation.header) { document.getElementById('header-representation').innerHTML = representation.header ; }
                if (representation.page) { document.getElementById('page-representation').innerHTML = representation.page ; }
                //if (representation.portfolio) { document.getElementById('page-portfolio').innerHTML = representation.portfolio ; }
                if (representation.footer) { document.getElementById('footer-representation').innerHTML = representation.footer ; }
                initElements(representation.portfolioImage) ;
                checkBackButton(model) ;
          }

          var components = {
              updates: [],
              actions: [],
              filters:[],
              postProcessings: [],
              intents: [],
              states: {
                  "ready": {
                          nextAction: () => {return true;}
                      }
                },
              data: data
          } ;


          // add actions and updates
          init(components) ;

          var options = {
              host : "http://localhost:5825"
          } ;

          // wire the elements of the pattern
          state.init(view,theme, display, components) ;
          model.init(state, components, options) ;
          actions.init(model.present, options) ;
          components.actions.forEach(function(action) {
              actions[action.name] = function(data, present) {
                  return action.implementation(data, present, model) ;
              }
          }) ;


          view.init(model,theme(options,language.substring(0,2).toLowerCase()))

          a = actions ;

          // render initial state
          state.representation(model) ;



    </script>
Rodrigo Carranza
@DrecDroid
Jan 26 2017 14:46

Thanks for all the insights about my work. I was thinking on React more as the Sin V = S(M) than the V, I took the V as the resulting HTML so React is far from being the result, it is in fact a function that receives the Model(through props) and renders the View. That's why I pass the model and the actions to the renderer. In your example where you put model.update.p = true I think that is coupling the view into the model, because now I need to know about how my theme renders and that could change if I rewrite my theme entirely. My proposal is to have a temporal record where to place state changes, I could place that in the model itself but It could create conflicts with the normal data in the model, that's why I divide the model in model and state, or think it better as model.data and model.state and then I pass to the state.representation function that I'm calling renderer the model, a function that checks the state, and the actions to inject them into the view.

In my code there is this method of tioSAM

  _checkState = (...states) => {
    if(states.length === 0) return true;

    for(let state of states){
      if(this.state.get(state)){
        return true;
      }
      if(this.learners[state] && this.learners[state]()){
        return true;
      }
    }
    return false;
  }

This is the function I pass to the renderer like renderer(model, checkState, actions).
It will check if any of the states was set in the this.state Map(), if not I'll try to derive It from the learners, the learners are function of the form learner(model, checkState).
I thought that that was the best approach, but now I'm realizing with a TodoList example I writing with the lib that It can't know If an specific TodoItem have changed It can only knows if the entire TodoList have changed somehow. Also in my tioSAM-react connect function It can only know if an update is necessary but not If some component can be rendered or not. I'll try to improve that _checkState function.

Jean-Jacques Dubray
@jdubray
Jan 26 2017 14:46
The /app/v1/model.js is actually an API call, I just found it was convenient to return it as an ES6 module

@DrecDroid

I think that is coupling the view into the model, because now I need to know about how my theme renders and that could change if I rewrite my theme entirely.

yes, I accept the critic, but there is no justification for that coupling other than it was faster to code in simple cases. It's all a question of performance, there is a point where you need to be so granular that it's better to use a vdom library. Here it's really coarse (header, page, footer)

Jean-Jacques Dubray
@jdubray
Jan 26 2017 14:52

it better as model.data and model.state

I would rather see it as model.data and model.changeSummary

Depending on the approach it's difficult to authoritatively say where this kind of optimization happens. It is just an optimization and perhaps the best way is to use a vdom library.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 14:55
That fits better, or model.changes, but that is internal to the system, the only thing I expose Is the function that check certain changes.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 14:55
It has nothing to do with the pattern itself. It's a bit like Redux using immutable.js, that's just an optimization. It does not conceptually change the programming model.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 15:01
@DrecDroid you lost me, too many "state". I would just say that what I suggest doing is just a hack, and I would definitely go to vdom as soon as you feel the hack does not scale well enough. I do use it reguarly and it works well enough from what I can see. For instance I built this app for a guy in my co-working space, it's 100% SAM based, you'll see that it's pretty fluid, even when you use the slider.

I thought that that was the best approach, but now I'm realizing with a TodoList example I writing with the lib that It can't know If an specific TodoItem have changed It can only knows if the entire TodoList have changed somehow.

You really have to think in terms of user experience, if the UX is good enough with a re-rendering a good chunk of the UI, why try to optimize further?

Rodrigo Carranza
@DrecDroid
Jan 26 2017 15:06
Maybe I'm just overthinking that fine-grained update/rendering, at first I just rendered the whole page with ReactDOM.render on the same node but I've found that It updates every component except those that one explicitly says not to rerender. But I have to say, SAM solves many of the problem I've encountered when working with Redux , I've also tried with MobX, but was also not the best solution, of course these are implementations but people tend to use them as a pattern, there is this problem when you don't know where to execute side-effects with those libs.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 15:07
yes, exactly, really look at it from the UX example. You cannot implement a vdom algorithm equivalent that way
@DrecDroid thank you! my point exactly, I always felt that React/Redux introduced some fantastic concepts, but they are missing a stronger foundation, that community's culture seems to be fixing one problem at a time, without ever looking back at the big picture.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 15:11
Now I have to go back to work, thanks for all the help today and for this awesome pattern I hope some day people realize It is the solution to many of their needs.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 15:11

you don't know where to execute side-effects with those libs.

Yes, that's a big one! perhaps the biggest one. I built an IoT parking solution with React two years ago and that was so painful, I just could not believe it. Even Redux punted on that problem.

thank you!
Jean-Jacques Dubray
@jdubray
Jan 26 2017 16:41
Not sure when the React team will specify a reasonable direction for what it is they are trying to do or if at the standup every morning they just vote on the "hack of the day": https://twitter.com/dan_abramov/status/824308413559668744
devin ivy
@devinivy
Jan 26 2017 19:16
what's wrong with defining general-purpose mutations?
Jean-Jacques Dubray
@jdubray
Jan 26 2017 19:28
@devinivy can someone points me to an article describing the React/Redux programming model?
Fred Daoud
@foxdonut
Jan 26 2017 20:53
@jdubray what do you think of using addEventListener instead of onclick in the HTML to avoid the leak?
Rodrigo Carranza
@DrecDroid
Jan 26 2017 21:38
@jdubray the NAP should be executed after the view is generated or asynchronously to the view generation?
devin ivy
@devinivy
Jan 26 2017 21:40
i believe it should be after the new state has been computed and before rendering the view.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 21:43

I've found this

       render(model,next) {
        console.log(model) ;
        this.representation(model,next) ;
        this.nextAction(model) ;
    }

https://github.com/jdubray/sam-samples/blob/master/vanilla-es6-boilerplate-project/html/sam/state.js

V = S(vm(M.present(A(data)), nap), so nap is a callback, but necessarily executed after de representation generation?
devin ivy
@devinivy
Jan 26 2017 21:47
i believe nap() plays into SAM's idea of a "step" so i think it should be able to cause work to be done before the view is updated.
Rodrigo Carranza
@DrecDroid
Jan 26 2017 21:57
Yes It makes sense but still have It no clear
Rodrigo Carranza
@DrecDroid
Jan 26 2017 22:07
My previous message was lost. This is from the website: "Once the state representation is rendered, the State is responsible for invoking the next-action predicate (nap), which is a pure function of the model. "
devin ivy
@devinivy
Jan 26 2017 22:11
yes, rendering the view should not wait for it to complete if the nap() performs something asynchronous. it would be up to the next "loop"/"step" to wait for those actions to complete.
the nap() is just a hook to respond to state updates not only with a new view, but also some new actions.
@jdubray will probably weigh-in with the nuances there, but that's the basic idea.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 22:32
@foxdonut wouldn't it leak the other way? I'd have to know which #id to add the event to?
Fred Daoud
@foxdonut
Jan 26 2017 22:33
that's true... :(
Jean-Jacques Dubray
@jdubray
Jan 26 2017 22:36
@DrecDroid @devinivy we have debated that question before, I don't have a strong opinion. It seemed a tiny be logical to render first and nap() second, but the rendering is in itself a next-action, it is just that it so common that you deal with it separately.
devin ivy
@devinivy
Jan 26 2017 22:38
it reminds me of componentWillUpdate() in react. i love this lifecycle event. you have an opportunity to change the state of your component before it renders.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 22:39
nap is outside the step itself, the step is Action->Model->State representatio. nap represents "automatic actions", i.e. given a state/status of the system an action is expected to occur. The reality is you might find that you want to order the next action(s) and rendering could logically happen between two next actions. I prefer the automatic actions to happen afterwards because I put a bit of JQuery code (data picker initialization) in the nap().
Rodrigo Carranza
@DrecDroid
Jan 26 2017 22:41
But they have to be sync to each other?. So nap(model).then(()=>render(model)) or render(model).then(()=>nap(model)) or simply nap1(model).then(()=>nap2(model)).then(()=>nap3(model)) where render function can be a nap
Jean-Jacques Dubray
@jdubray
Jan 26 2017 22:41
yes, all this is linked, it's not like SAM is doing things you cannot do otherwise or no one ever thought of this, but I believe that SAM provides the simplest programming model.
yes! I liberally use setTimeout to "sync" them, but that's a hack.
I am sure there is a better way, but I am still catching up on front-end idiosyncracies.
As I mentioned the rendering/napping is not as rigid as the action->model->state step. I have generally been able to solve all rendering questions with V=f(m)+nap
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:38
I want to be sure where is the place of React in the expression V = S( vm(M.present(A(data))), nap(Model) ). Right know I taking React as the vm function, because It takes the model and knows how to handle It.
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:44
I would have seen React (alone) to be S()
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:44
reactRenderer = function(component, element){
  return function(model){
    return Promise((accept, reject) => {
      ReactDOM.render(
        component(model),
        element,
        ()=>accept()) //a callback that executes when the rendering is done
      )
    }
  }
}

system.renderer = reactRenderer(component, element)
so my state function is
this.state(model){
  this.renderer(model).then(nap(model))
}
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:44
vm is more a Redux selector
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:46
vm is a function that transform a copy of the model to fit the State function?
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:46
yes, it's to avoit polluting the model with these kinds of properties
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:47
oh, so It's not strictly neccessary
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:47
no, totally optional
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:47
How V=f(m)+nap translates to implementation?
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:48
The only thing I am a bit uncomfortable with your code is the coupling component/model
I know a lot of code samples tend to be lazy and pass the whole model, but that's a complete anti-pattern. All the theme.js samples I provide express the view components from a neutral point of view. Once should be able to crank out a library of view components (a theme) and reuse these components at will (or change the theme).
V = f(M) depends on the Web framework, conceptually that's what you want to achieve. Stateless React components are pretty close to that model, that's what I had found originally very attractive in React (along with JSX).
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:52
the State does not hold any “state”, it is a pure function which computes the view and the next-action predicate, both from the model property values.
"compute the view" is exactly what React does
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:53
Correct, if you are bothered by "state" think of it as "state representation"
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:54
that's why I consider React or any renderer as part of the state, then comes the nap
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:54
Yes, as I said/written, that part is directly inspired from React. In Angular2 (with templates) you cannot do that, so you have to use a pub/sub mechanism to update the component properties
I see the disconnect, one second
Unlike React, the state function's main role is to derive the "status" of the system, which is also called "state" (e.g. a car is started). That status/state will inform how to compute the state representation and the next actions.
What you will see often in the code I write, is something like this:
state.representation = function(model) {
    var representation = 'oops... something went wrong, the system is in an invalid state' ;

    if (state.ready(model)) {
        representation = state.view.ready(model) ;
    } 

    if (state.counting(model)) {
        representation = state.view.counting(model) ;
    }

    if (state.launched(model)) {
        representation = state.view.launched(model) ;
    }

    if (state.aborted(model)) {
        representation = state.view.aborted(model) ;
    }

    state.view.display(representation) ;
}
That would be the role of a container component in React?
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:58
yes, kind of
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:59
The theme's would be called with the View's ready, counting, launched... methods
At that point you are outside the SAM pattern, SAM stops at the (state) representation
Rodrigo Carranza
@DrecDroid
Jan 26 2017 23:59
I feel like there is this problem inside State function, It could be nap then render or render then nap, but you've said that in fact the rendering is a nap. So why not better consider V = (...naps) where a nap is of the form nap = State(Model.present(A(data))
Jean-Jacques Dubray
@jdubray
Jan 26 2017 23:59
Anything beyond that would heavily depend on your Web framework