tempusfugit (139) [Avatar] Offline
#1
Page 3(7):
1.1.2 The model–view–view-model pattern

MVVM originated from Microsoft's Windows Presentation Foundation (WPF). It was introduced as a variation of MVC but it's actually the Presentation Model with added data binding. The view-model's primary claim to fame is that it doesn't directly access the view - it simply exposes easily bindable properties and methods.

"Though it sounds View-ish is really more Model-ish" - becomes clear when looking at Presentation Model:
Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation. The Presentation Model coordinates with the domain layer and provides an interface to the view that minimizes decision making in the view. The view either stores all its state in the Presentation Model or synchronizes its state with Presentation Model frequently.
The interesting thing is that the Model in MVVM is somewhat ambiguous. In Classic MVC, Server MVC, Presentation Model and even WPF's MVVM the Model is understood to be "core functionality and data" - and in modern web applications the core functionality resides behind the server-side API. This is the perspective that Figure 1.2 takes. However in the context of SPAs, I've come across MVVM as being described as an entirely client-side affair where the Model is simply the naked data that is read from and written to server-side API.

Now in the context of Vue.js it doesn't really matter as it primarily focuses on the view-model and data binding - but when discussing MVVM as such, one interpretation has the scope of the entire web application while the other remains confined to the UI in the browser (i.e. has the scope of the SPA).

Further note that currently Vue.js only claims to be inspired by MVVM where the Vue instance takes a role equivalent to that of the view-model.

Page 3(7):
Presentation logic often mirrored business logic.
And it continues to do so with respect to data validation because the server API should never rely on the client for validation - and any other business logic/capability still belongs on the server-side.

Page 3(7):
MVVM provides a blueprint for us to build applications with more responsive user interaction and feedback
The use of the unqualified term "application" is problematic here. Based on the scope of MVVM indicated by Figure 1.2 "web application" is implied. But with the "alternate" interpretation of MVVM it would imply "single page application". And given that Vue.js
can be incorporated into an existing webpage for very simple tasks
(Page 8(12)) the presence of an SPA isn't even a given. So there seems to be a lot of opportunity for confusion for the relatively simple statement that "view-model based Web UIs can feel much more responsive to the user".

Page 3(7):
while avoiding costly duplication of code and effort across the overall architecture.
The primary benefit is that the view-model is easier to unit test. As it's actually stated that MVVM is overkill for simple UIs** I suspect that the argument that it "avoids costly duplication of code and effort across the overall architecture" doesn't hold water. And what is "architecture" in this context? The structure of the presentation/UI? In fact one could take the position that MVVM increases complexity as the ViewModel has to mediate the finer-grain actions of the User against the view through courser grained service requests against the domain-layer - now that complexity could possibly be mitigated with the right library (or framework). Universal/Isomorphic Javascript in combination with Vue.js could avoid some duplication but only by accepting all the operational disadvantages that Node.js on the backend brings.

** Also re-iterated by Addy Osmani.

Page 4(8):
... removing the latency associated with a network operation between client and server, the design of MVVM ...
It would be more accurate to state that MVVM helps to remove the impact of network operations from user perception (given that network operations (which are subject to latencies) are still required).

Page 4(8):
Figure 1.2
Lots of unqualified references to "application" leaving the ambiguity between web application and SPA.
  • ... current application state. - SPA?
  • ... state of the application. - SPA?
  • ... our application's data. - web application?
  • ... with the application. - SPA?
  • Instead views render content based on the presence and quantity of data in the current application state.
    I think "Instead views render relevant data found in the view-model" would be less ambiguous.
    The view-model retains a representation of application data in ... a store. ...
    I don't think you are giving the view-model enough credit here. It is also responsible for maintaining presentation data that the domain-layer isn't necessarily interested in - like which controls are currently enabled. Furthermore it can contain behaviour to change that presentation-related data (on top of any synchronization logic implied in the other callout).
    The view-model, similar to a controller, retains the job of persisting data to the model.
    Actually the view-model is seen as an abstraction of the view though the typical lack of view-controller separation may have had something to do with that. The (server-side) model ultimately gets to decide what is actually persisted - that is not the view-model's job - the view-model communicates with the model to obtain the data that it requires and to inform the model of the requested actions.
    However such transactions need not be synchronous, allowing users to continue interacting with the application
    Careful, transactions imply the inherent capability to rollback to a previous state - so "interaction" would be more appropriate ("allowing the application to remain responsive"). Also the observed asynchrony applies largely from the perspective of the single browser GUI thread running the JavaScript code - whenever an HTTP request/response is used to contact the domain-layer the interaction is still considered synchronous. Events arriving via a web socket are a different matter.
    In some end-to-end JavaScript architectures the model acts strictly as a store without any logical restrictions imposed on incoming data, instead of shifting any business logic decisions to the view-model.
    1) I don't think this adds any clarification to the figure. 2) Domain logic can be more easily shared among multiple frontends if it resides in a server-side service. When the domain logic needs to be altered only a single service needs to be updated rather than all the various frontends that use the domain data.
    As you can see from Figure 1.2, the view-model also wears quite a few different hats. This consolidation of responsibility has a single, profound implication for our application’s views: when data changes in the view-model, any view bound to it is automatically updated.
    It is my observation that updating the view-model from the view (via methods) and the view from the view-model (via properties) is ultimately the responsibility of the binder - which isn't mentioned in that passage.

    1.1.3 What is a reactive application anyway?
    I think in the context of Vue.js it makes sense to restrict the "reactive" discussion to the concepts that are talked about in the Reactivity in Depth page on the Vue.js web site - discussing reactive programming at large is gratuitous.

    From what I recall it was ReactiveX (Rx in JS) which started the whole "reactive programming" frenzy ("Functional Reactive Programming" (FRP) is an entirely different animal and dates back to 1997).

    Both ReactiveX and Vue.js use ideas from the observer pattern but that's about where the commonalities end.

    ReactiveX is about Observables; for example Single is a very specialized variant - it's essentially a Promise.

    Vue.js uses "reactive" to very generally qualify data that is "watched" for the purpose of propagating value changes to potential "observers" - while it is descriptive of Vue.js's design approach, it has very little to do with reactive programming. While there is "propagation of change" there don't seem to be any "data streams" - the "queue" seems to collect all changes that are gathered during a single event loop for the purpose of "de-duplication" and batching a single DOM update.

    It may also may be a good idea to quote the "massive sink of human knowledge" with a lot more restraint. While the existance of Wikipedia is invaluable, the quality of it's content is uneven and always needs to be scrutinized with a certain amount of skepticism. More often than not the "External Links" and "References" sections are the most valuable sources of information of an Wikipedia entry as they can lead to authoritative sources - which are the ones that should actually be quoted and referenced.

    So in this section I think it makes more sense to link to Reactivity in Depth rather than Reactive Programming.

    Page 5(9)
    1.1.4 A JavaScript calculator
    ... in plain Vanilla JavaScript - Listing 1.2
    Even considering that this example is supposed to be introductory and relatively "basic", it's style could be considered a bit "retro" and "lowbrow" for modern "Vanilla JavaScript" in 2017 - and I'm not even talking about ES2015. As far as a "first impression" goes for potentially promoting "good coding practices" throughout the entire book, I'd consider it a bit of an underachiever. The book's product page does target a "beginning to intermediate JavaScript, HTML, and CSS" audience - but I can't get past the impression that Vue.js has a strong affinity with Webpack 2 (regardless of assurances that Vue.js can be used with other build tools) - in which case one better be prepared to ramp up one's understanding of modern JavaScript and its associated practices.

    So just some things I noticed right of the bat (ignoring including the JavaScript and CSS inside the HTML page):
  • Item 13: Use Immediately Invoked Function Expressions to Create Local Scopes (David Herman: Effective JavaScript (2013))
  • Avoid Event Handler Content Attributes. (http://www.jibbering.com/faq/names/event_handler.html)
  • Use Event delegation https://davidwalsh.name/event-delegate (2011)
  • Reserve id attributes for things that are truly unique like container instances - use class for everything else, especially parts that could occur repeatedly in various instances of the same container (component).

  • So an ES5 version could look something like this:
    <!DOCTYPE>
    <!-- file: calculatorES5.html -->
    <!-- Google Chrome Version 59.0.3071.115 (Official Build) (64-bit) -->
    <html>
      <head>
        <title>A JavaScript Calculator</title>
        <style>
         p, input { font-family: monospace; }
         p, { white-space: pre; }
        </style>
      </head>
      <!-- Bind to the init function -->
      <body>
        <div id="myCalc">
          <!-- Form inputs to collect x and y and bind
               to the runCalc function -->
          <p>x <input class="calc-x-input" value="0"></p>
          <p>y <input class="calc-y-input" value="0"></p>
          <p>--------------------</p>
          <!-- Display the result of x + y -->
          <p>= <span class="calc-result"></span></p>
        </div>
        <!-- Our calculator's JavaScript -->
        <script type="text/javascript">
         (function(){
    
           // Constructor to create calc instances
           function Calc(xInput, yInput, output) {
             // store element references in calc instance
             this.xInput = xInput;
             this.yInput = yInput;
             this.output = output;
           }
    
           // store property names for HTMLInputElement references
           // on Calc constructor
           Calc.xName = 'xInput';
           Calc.yName = 'yInput';
    
           Calc.prototype = {
             render: function (result) {
               this.output.innerText = String(result);
             }
           };
    
           // Constructor to create values for a calc instance
           function CalcValue(calc, x, y) {
             this.calc = calc;
             this.x = x;
             this.y = y;
             this.result = x + y;
           }
    
           CalcValue.prototype = {
             copyWith: function(name, value) {
               var number = parseFloat(value);
    
               if (isNaN(number) || !isFinite(number))
                 return this; // value isn't a number
    
               if (name === Calc.xName)
                 return new CalcValue(this.calc, number, this.y);
    
               if (name === Calc.yName)
                 return new CalcValue(this.calc, this.x, number);
    
               return this;
             },
             render: function() {
               this.calc.render(this.result);
             }
           };
    
           // Initialize Calc component
           function initCalc(elem) {
    
             var calc =
               new Calc(
                 elem.querySelector('input.calc-x-input'),
                 elem.querySelector('input.calc-y-input'),
                 elem.querySelector('span.calc-result')
               );
             var lastValues =
               new CalcValue(
                 calc,
                 parseFloat(calc.xInput.value),
                 parseFloat(calc.yInput.value)
               );
    
             // event handler that updates the result
             var handleCalcEvent =
               function handleCalcEvent(e) {
                 var newValues = lastValues,
                     elem = e.target;
    
                 switch(elem) {
                   case calc.xInput:
                     newValues =
                       lastValues.copyWith(
                         Calc.xName,
                         elem.value
                       );
                     break;
                   case calc.yInput:
                     newValues =
                       lastValues.copyWith(
                         Calc.yName,
                         elem.value
                       );
                     break;
                 }
    
                 if(newValues !== lastValues){
                   lastValues = newValues;
                   lastValues.render();
                 }
               };
    
             elem.addEventListener('keyup', handleCalcEvent, false);
    
             return lastValues;
           }
    
           // Initializes the calculator myCalc
           window.addEventListener(
             'load',
             function() {
               var cv = initCalc(document.getElementById('myCalc'));
               cv.render();
             },
             false
           );
    
         }());
        </script>
      </body>
    </html>
    
    or the equivalent ES2015 version (not that I'm a fan of class-orientation - especially in JavaScript - but I'm still going to take advantage of the notational conveniences)
    <!DOCTYPE html>
    <!-- file: calculatorES2015.html -->
    <!-- Google Chrome Version 59.0.3071.115 (Official Build) (64-bit) -->
    <html>
      <head>
        <title>A JavaScript Calculator</title>
        <style>
          p, input { font-family: monospace; }
          p, { white-space: pre; }
        </style>
      </head>
      <!-- Bind to the init function -->
      <body>
        <div id="myCalc">
          <!-- Form inputs to collect x and y and bind
               to the runCalc function -->
          <p>x <input class="calc-x-input" value="0"></p>
          <p>y <input class="calc-y-input" value="0"></p>
          <p>--------------------</p>
          <!-- Display the result of x + y -->
          <p>= <span class="calc-result"></span></p>
        </div>
        <!-- Our calculator's JavaScript -->
        <script type="text/javascript">
         (function(){
    
           class Calc {
             constructor(xInput, yInput, output) {
               this.xInput = xInput;
               this.yInput = yInput;
               this.output = output;
             }
    
             static xName() { return 'xInput'; }
             static yName() { return 'yInput'; }
    
             render(result) {
               this.output.innerText = String(result);
             }
           }
    
           class CalcValue {
             constructor(calc, x, y) {
               this.calc = calc;
               this.x = x;
               this.y = y;
               this.result = x + y;
             }
    
             copyWith(name, value) {
               const number = parseFloat(value);
    
               if (isNaN(number) || !isFinite(number))
                 return this; // value isn't a number
    
               if (name === Calc.xName())
                 return new CalcValue(this.calc, number, this.y);
    
               if (name === Calc.yName())
                 return new CalcValue(this.calc, this.x, number);
    
               return this;
             }
    
             render() {
               this.calc.render(this.result)
             }
           }
    
           // Initialize Calc component
           function initCalc(elem) {
    
             const calc =
               new Calc(
                 elem.querySelector('input.calc-x-input'),
                 elem.querySelector('input.calc-y-input'),
                 elem.querySelector('span.calc-result')
               );
             let lastValues =
               new CalcValue(
                 calc,
                 parseFloat(calc.xInput.value),
                 parseFloat(calc.yInput.value)
               );
    
             // event handler that updates the result
             const handleCalcEvent =
               (e) => {
                 let newValues = lastValues,
                     elem = e.target;
    
                 switch(elem) {
                   case calc.xInput:
                     newValues =
                       lastValues.copyWith(
                         Calc.xName(),
                         elem.value
                       );
                     break;
                   case calc.yInput:
                     newValues =
                       lastValues.copyWith(
                         Calc.yName(),
                         elem.value
                       );
                     break;
                 }
    
                 if(newValues !== lastValues){
                   lastValues = newValues;
                   lastValues.render();
                 }
               };
    
             elem.addEventListener('keyup', handleCalcEvent, false);
    
             return lastValues;
           }
    
           // Initializes the calculator myCalc
           window.addEventListener(
             'load',
             () => {
               let cv = initCalc(document.getElementById('myCalc'));
               cv.render();
             },
             false
           );
    
         }());
        </script>
      </body>
    </html>
    
    Now with a larger code base I would be hiding those constructors inside factory functions (instead of Item 33: Make Your Constructors new-Agnostic). Note also that by not going with a more elaborate JavaScript version it's less clear that the Vue.js version is much more compact (as it should be) than the "Vanilla" version.

    Page 6(10)
    1.1.5 A Vue calculator
    Listing 1.3
    Again an IIFE would be appropriate and the calc variable really doesn't serve any purpose in this context. Furthermore it may be a good idea to implement the Vue instance data member as an object factory rather than a plain object - it's a requirement for Vue components anyway and as long as it works in all other situations it seems like a good habit to get into from the get-go. Using ES2015 the shorthand method definition syntax could then clean up both the data and result method definitions.
    <!DOCTYPE html>
    <!-- file: calculatorVue2.html -->
    <!-- Google Chrome Version 59.0.3071.115 (Official Build) (64-bit) -->
    <!-- Vue.js 2.3.4 -->
    <html>
      <head>
        <title>A Vue.js Calculator</title>
        <style>
         p, input { font-family: monospace; }
         p, { white-space: pre; }
        </style>
      </head>
      <body>
        <!-- A DOM anchor for our app -->
        <div id="app">
          <!-- Form inputs to collect x and y -->
          <p>x <input v-model="x"></p>
          <p>y <input v-model="y"></p>
          <p>---------------------</p>
          <!-- Display the result of x + y -->
          <p>= <span v-text="result"></span></p>
        </div>
    
        <!-- The vue.js library -->
        <script src="https:/unpkg.com/vue/dist/vue.js"></script>
        <!-- Our calculator's JavaScript -->
        <script type="text/javascript">
         // Initializes the calculator
         (function(){
    
           function isNotNumericValue(value) {
             return isNaN(value) || !isFinite(value);
           }
    
           new Vue({
             el: '#app', // Connects our app to the DOM
             data() {    // *** return data in a closure - using ES2015 method definition shorthand
               return {  // *** i.e. use an object factory to be in the habit for components
                 x: 0,
                 y: 0,
                 lastResult: 0
               }; // Variables for the value of x and y
             },
             computed: {
               result() { // *** ES2015 function shorthand
                 let x = parseFloat(this.x);
                 if(isNotNumericValue(x))
                   return this.lastResult;
    
                 let y = parseFloat(this.y);
                 if(isNotNumericValue(y))
                   return this.lastResult;
    
                 this.lastResult = x + y;
    
                 return this.lastResult;
               }
             }
           });
    
         }());
        </script>
      </body>
    </html>
    

    Page 8(12)
    1.1.7 How does Vue facilitate MVVM and reactivity?
    Figure 1.4
    We use JavaScript to describe the data represented by a view-model, as well as the properties and methods exposed by the data binding.
    Shouldn't that be something like "... by a view-model which exposes properties and methods for data binding"?

    Page 9(13)
    Crucially, this means that we do not depend on client-server communication for updated views, executing business logic, or any other task that falls under the domain of the view or view-model.
    I think it would be more appropriate to state that updated views do not require server-based page reloads. The context in which SPAs are most often used is in fact most appropriately classified as client-server where SPAs are used to implement thin clients (with a rich UI) while at the same time the actual domain/business logic is buried somewhere behind the APIs that the SPA uses (i.e. inside the (server-side) "model").
    ... relative to the MVC architecture is that control of the application never leaves the browser.

    "... relative to the Server MVC architecture is that the browser page has to rarely, if ever, reload during the user's entire session."
    ... Vue provides a solid bedrock for any reactive web application.
    As far as I can tell the Vue.js site doesn't use the term "reactive web application". It limits is use of "reactive" to describe the responsiveness of the user experience that results from the behaviour of the Vue instances and data binding. Before "Responsive Web Design" and ReactiveX, "responsiveness" was the term in use. So "reactive web application" simply comes across as "buzzword compliance".

    1.2 So, why Vue.js?
    Our team is not strong at web development ... HTML, CSS and JavaScript, familiar tools that allow you to be productive right from the get-go.
    Usually "not strong in web development" means "not overly familiar with HTML, CSS, and JavaScript and the associated tools". Maybe you are suggesting that with Vue.js they can focus on the "moving parts" of the web application while a/some web designer/s could help with the HTML/CSS?
    No complicated build tools required! Getting a prototype in front of users can happen within a week or two of starting development, allowing you to gather feedback early and to iterate often.
    While it's probably true that no complicated build tools are required to get started, I would be inclined to believe that those who are endeavouring to build a prototype in one to two weeks are already familiar with the required tool chain. The idea of the tool chain is to automate trivial and repetitive tasks so that you can spend more time on the important stuff.

    Page 11(15):
    1.3 Summary
    ... but we’ve seen that building a reactive application isn’t as intimidating as some of the jargon can make it feel.
    reactive application is jargon - and not particularly descriptive at that.
    Here’s a few things I think about when I look at the app.
  • How can we provide a friendlier default value than ADJECTIVE or NOUN?
  • How would we eliminate the need to repeat text strings, like “ADJECTIVE,” in so many
    places?
  • Can we clear the default input when a user focuses on an input? What about restoring
    it if they leave the field blank?
  • Is there a way to avoid hand-coding each input?
  • What does all of this refer to? Was there once a different application than the one we are now seeing in Listing 1.3 on page 6(10)?
    Erik Hanchett (34) [Avatar] Offline
    #2
    Thanks again for your advice! I'll be going over chapter 1, line by line, with your comments in mind. The MVVM patter needs to be updated.

    As for updating the calculator code, I think it could be cleaned up. This chapter is geared towards a beginner in JavaScript, although I understand your point with best practices.
    Erik Hanchett (34) [Avatar] Offline
    #3
    By the way, I've updated the descriptions of MVVM and MVC based your feedback. I've tried to separate the classic MVC, that you've described, and the client-side MVC/Server side MVC that we have today. Hopefully, that helps the reader understand that the MVC that is described in Chapter 1, isn't the classic MVC that was created in 1979.

    The code examples are great! I'm using part of them in the book, to show the differences now. They might get tweaked before the final release, I appreciate your help!