Quantcast
Viewing all articles
Browse latest Browse all 10

SAM – A Functional approach to UI Construction



SAM is the “State-Action-Model” pattern, a new reactive pattern which seems to have interesting properties when building User Interfaces (Web or otherwise). SAM’s foundation is TLA+, so it can be used for many other things, but in this series of posts, I take a look at how we can use SAM to construct modern, OmniChannel User Interfaces.

The whole idea behind the pattern is to create a much simpler (when compared to MVC) feedback loop between the Model and the View:

Image may be NSFW.
Clik here to view.
sam

In essence, and in this context, SAM borrows many ideas from React.js, but without the ceremony that React comes with. The big game changer here is frameworks like bootstrap that allow us to decouple the “components” from the “page”. It would be harder to use SAM with a pure “page” formalism.

Let’s talk a bit about the ceremony introduced by React. I understand if I had to build Instagram, I would need that kind of framework, but that’s not for everyone. When you consider the burden of adopting and learning React, you may want to think twice before you burry your feet into it.

One of React’s original design goals was to be non invasive to the Web developer with familiar HTML-like extensions (via JSX):

//
var data = [
  {id: 1, author: "Pete Hunt", text: "This is one comment"},
  {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
];

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox and here is a comment {this.props.data[0].text} by {this.props.data[0].author} 
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox data={data} />,
  document.getElementById('content')
);
//

 

But, a React component is not much more than a simple function call (var content = commentBox(data)).

That component-based functional connection between the State Representation and the Model would make React a perfectly fine way to implement SAM, if it were not for a couple of details:
– the way React allows you to push some of state (Model) in the view
– the lack of action semantics in the component model (it’s just not enough to say that React “is the view in MVC”)

Once you understand how SAM works, you realize how horrific React’s stateful architecture, I can’t think of a worse place to manage state than the view. The only “state” that should be known to a React component is its own configuration, with no relationship whatsoever to the model, that kind of code (stripped from Facebook’s documentation) is a complete anti-pattern:

var FilterableProductTable = React.createClass({
  getInitialState: function() {
    return {
      filterText: '',
      inStockOnly: false
    };
  },

  render: function() {
    return (
      <div>
        <SearchBar
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
        <ProductTable
          products={this.props.products}
          filterText={this.state.filterText}
          inStockOnly={this.state.inStockOnly}
        />
      </div>
    );
  }
});

 

What React got right (the reactive relationship between the Model and the View) they took it all back by enabling the spread of the model in the view. The view’s role should be to provide a representation of the model and enable the user to trigger actions, “intentionally”. Beyond that, there is no justification whatsoever, for a tighter relationship between the model and the components of the view.

Actions are the other big issue in React. The React team made it a complete after-thought, with no direct support for them in their component model. That massive oversight lead to a couple “patterns” (and associated libraries) Flux and most recently to Redux. Actions should be allowed to make third party API calls (any API call unrelated to the model) and the model, upon a new set of values, can decide how to persist itself or fetch more data (in particular via related API calls).

With SAM, the State Representation is a function of the Model to which Actions present values computed from a set of data that may or may not depend on the Model.

//
Sr = f( 
       M.present(
            A(
               data(M)
             )
      ) ;
//

 
But, and this is a big “but”, the Model decides how and if it needs to update itself from the presented values. Actions have no decision power as to setting the state. In particular that means that when a user fires an intent to trigger an action, the action itself is purely functional, 150% functional, with again, no state whatsoever. In essence, SAM makes the React Architecture pretty much useless, 95% of React can be achieved with a simple JavaScript function. Ok, Ok… I also find the Virtual DOM amazing, but how many “apps” truly need it? Instagram, ok, I get it, Facebook, why not? SalesForce? SAP? your app? Nothing would prevent us to add a virtual DOM in the SAM implementation.

SAM as it stands is really simple, I don’t think one could find a simpler way to build Single Page Applications, yet it provides some of the best benefits of React + Redux without the weight of a framework.

So, how would a SAM-based application look like. Let’s take a simple example with my “about” page that I had originally constructed as a boring wordpress page and that is now a SPA running on Node.js. Note that the code shown here is strictly isomorphic and could run just as well in the browser when mounted properly. To write this code, I started from a raw HTML5 template, and created a series of JavaScript functions as part of a theme object:

//
var theme = {} ;

theme.education = function(e) {
   e = e || {} ;
   e.img = e.img || 'http://placehold.it/1671x786' ;
   e.summary = e.summary || 'SUMMARY' ;
   e.quote = e.quote || 'QUOTE' ;
   e.img = e.img || 'http://placehold.it/1671x786' ;
   var items = e.items || [{}] ;
   
   var itemJoin = join(items.map(function(item) {
       item = item || {} ;
       item.number = item.number || '01' ;
       item.school = item.school || 'SCHOOL' ;
       item.degree = item.degree || 'DEGREE' ;
       item.period = item.period || 'PERIOD' ;
       return ('<!-- education item -->\n\
                <div class="col-md-6 col-sm-6 padding-fifteen no-padding-lr no-padding-top xs-padding-thirteen xs-no-padding-lr xs-no-padding-top xs-display-inline-block xs-width-100">\n\
                    <div class="col-lg-2 col-md-2 col-sm-1 col-xs-2 no-padding">\n\
                        <span class="alt-font title-large light-gray-text">'+item.number+'</span>\n\
                    </div>\n\
                    <div class="col-md-10 col-lg-10 col-sm-11 col-xs-10 no-padding counter-style1">\n\
                        <span class="alt-font display-block text-uppercase black-text letter-spacing-1 margin-five no-margin-right no-margin-tb xs-no-margin">'+item.school+'</span>\n\
                        <span class="alt-font display-block text-uppercase line-height-20 medium-gray-text margin-five no-margin-right no-margin-bottom xs-no-margin">'+item.degree+'<br>'+item.period+'</span>\n\
                    </div>\n\
                </div>\n') ;
   })) ;
   
   return ('<!-- education section -->\n\
            <section id="education" class="wow fadeIn cover-background personal-about" style="background-image:url(\''+e.img+'\');">\n\
                <div class="container">\n\
                    <div class="row">\n\
                        <!-- section title -->\n\
                        <div class="col-md-10 col-lg-8 col-sm-6">\n\
                            <span class="title-sideline text-uppercase title-large alt-font black-text font-weight-600 border-color-fast-pink-dark">Education</span>\n\
                        </div>\n\
                        <!-- end section title -->\n\
                    </div>\n\
                    <div class="row">\n\
                        <!-- text -->\n\
                        <div class="col-md-10 col-lg-8 col-sm-12">\n\
                            <p class="text-large alt-font width-90 margin-fifteen no-margin-bottom no-margin-lr">'+e.summary+'</p>\n\
                            <div class="divider-line margin-fifteen no-margin-lr"></div>\n\
                        </div>\n\
                        <!-- end text -->\n\
                    </div>\n\
                    <div class="row">\n\
                        <div class="col-md-10 col-lg-8 col-sm-12">\n\
                        '+ itemJoin  + '\
                    </div>\n\
                    <div class="row">\n\
                        <div class="col-md-10 col-lg-8 col-sm-12">\n\
                            <div class="divider-line margin-fifteen no-margin-lr no-margin-top"></div>\n\
                        </div>\n\
                    </div>\n\
                    <div class="row">\n\
                        <div class="col-md-10 col-lg-8 col-sm-12">\n\
                            <span class="alt-font text-large text-uppercase quote-style1">'+e.quote+'</span>\n\
                        </div>\n\
                    </div>\n\
                </div>\n\
            </section>\n\
            <!-- end education section -->\n') ; 
} ;
//

As you can see, that kind of code is just plain vanilla JavaScript. What does the model look like?

//
model.education = {} ;
model.education.img = home.imageDir + 'penn-state.jpg' ;
model.education.summary = 'Avid learner, with a broad sense of abstraction and a passion for applied knowledge' ;
model.education.quote = 'One cannot look at the Sea without wishing<br>for the wings of a Swallow' ;
model.education.items = [
         { number: '01. ', school: 'Penn State', degree: 'Post-Doc', period: '1989-1991'}
        ,{ number: '02. ', school: 'University of Provence', degree: 'PhD', period: '1987-1989'}
        ,{ number: '03. ', school: 'Ecole Centrale de Lyon', degree: 'MSc', period: '1987-1986'}
        ,{ number: '04. ', school: 'Ecole Centrale de Lyon', degree: 'BSc', period: '1982-1986'}
    ] ;
//

 
What kind of benefit this pure separation between the model and the state representation brings? well, the model can be freely exposed as an API (yes, I know that looks a lot like Microformats), that means that when I join a team, their model can aggregate my information via a simple API call:

//
curl -H "accept: application/json" http://www.ebpml.org/api/v1/about/home
//

 

And, here is the architecture behind this code:

Image may be NSFW.
Clik here to view.

The blog section of the model is integrated via an API call (with a straight translation to JSON via xml2js) of the ebmpl.org wordpress feed. Pretty cool, hey? Ebpml.org itself is deployed on the Gliiph Platform and the About “page” is a Gliiph plugin, where the content of the About page is substituted with the content served from the SAM-based SPA.

Now, how do you “render” the model, you have different options, SAM does not prescribe a particular way, you can think in terms of pages if you’d like (as long as the model decides the next page to display, not the action), you can also think in terms of a single page applications with lots of if…then…else.

//
function stateRepresentation(model) {
    
    var output = (theme.html(
                  theme.head(
                       theme.headerLinks(model.title) 
                  )
                + theme.body(
                    theme.mainMenu(model.menuItems,model.logo)
                +   theme.container(   

                        model.personal ? theme.home(model.personal) : ''
                    +   model.about ? theme.about(model.about) : ''
                    +   model.skills ? theme.skills(model.skills) : ''
                    +   model.education ? theme.education(model.education) : ''
                    +   model.employment? theme.employment(model.employment) : '' 
                    +   model.portfolio ? theme.portfolio(model.portfolio) : ''
                    +   model.blog ? theme.blog(model.blog) : ''
                    +   model.contact ? theme.contact(model.contact) : ''
                    +   theme.footer(model.footer) 
                    ,["sidebar-wrapper","personal"]

                    )
                +   theme.scrollToTop()
                +   theme.lib(model.libs)
                          )
                      )  
                ) ;
    return output ;
} 
//

There is no need for an MVC framework. Yes, I know, as many readers have pointed out this is a pretty trivial case, but I am currently building an e-Commerce site for a client, with SAM, and there is simply no difficulty/constraints in applying the pattern, quite the contrary. There is also nothing specific about JavaScript and the exact same code could be written in Objective-C/Swift, Java or C#. The keys to applying the pattern correctly are:

  • 100% functional actions, which present values to the model
  • model drives 100% of the UI rendering
  • UI holds no state and triggers actions

If your application’s state machine is rather complex, making it a bit more difficult to decide what control state you’re in given a set of values from the model, I have created the STAR library. Again, the main difference with Redux is that it is not the action, like in traditional state machines, which decides of the resulting state, it is the model.

I’d be interested in your feedback. Since this seems reasonably new, do not hesitate to shoot at it.


Viewing all articles
Browse latest Browse all 10

Trending Articles