Snippets

Jean-Jacques Dubray Isomorphic JavaScript with SAM | the Blog Sample

Updated by Jean-Jacques Dubray

File blog.html Modified

  • Ignore whitespace
  • Hide word diff
 <html>
 <head>
-<title>SAM Sample | Blog Posts</title>
-<meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
-    <meta name="description" content="">
-    <meta name="author" content="">
+	<title>SAM Sample | Blog Posts</title>
+	<meta charset="utf-8">
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+	<meta name="description" content="">
+	<meta name="author" content="">
 
-    <title>Blog Template for Bootstrap Using the SAM Pattern</title>
+	<title>Blog Template for Bootstrap Using the SAM Pattern</title>
 
-    <!-- Bootstrap core CSS -->
-    <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
+	<!-- Bootstrap core CSS -->
+	<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
 
-    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
-    <link href="http://getbootstrap.com/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
+	<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+	<link href="http://getbootstrap.com/assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet">
 
-    <!-- Custom styles for this template -->
-    <link href="http://getbootstrap.com/examples/blog/blog.css" rel="stylesheet">
+	<!-- Custom styles for this template -->
+	<link href="http://getbootstrap.com/examples/blog/blog.css" rel="stylesheet">
 
-    <!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
-    <!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
-    <script src="http://getbootstrap.com/assets/js/ie-emulation-modes-warning.js"></script>
+	<!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
+	<!--[if lt IE 9]><script src="../../assets/js/ie8-responsive-file-warning.js"></script><![endif]-->
+	<script src="http://getbootstrap.com/assets/js/ie-emulation-modes-warning.js"></script>
 
-    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+	<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
     <!--[if lt IE 9]>
       <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
       <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
-    <![endif]-->
+      <![endif]-->
 
-</head>
-<body>
+  </head>
+  <body>
 
-<div class="blog-masthead">
-      <div class="container">
-        <nav class="blog-nav">
-          <a class="blog-nav-item active" href="#">Home</a>
-          <a class="blog-nav-item" href="http://sam.js.org">About</a>
-        </nav>
-      </div>
-    </div>
+  	<div class="blog-masthead">
+  		<div class="container">
+  			<nav class="blog-nav">
+  				<a class="blog-nav-item active" href="#">Home</a>
+  				<a class="blog-nav-item" href="http://sam.js.org">About</a>
+  			</nav>
+  		</div>
+  	</div>
 
-    <div class="container">
+  	<div class="container">
 
-      <div class="blog-header">
-        <h1 class="blog-title">The SAM Pattern Blog</h1>
-        <p class="lead blog-description">The official blog sample impleting the SAM Pattern</p>
-      </div>
+  		<div class="blog-header">
+  			<h1 class="blog-title">The SAM Pattern Blog</h1>
+  			<p class="lead blog-description">The official blog sample impleting the SAM Pattern</p>
+  		</div>
 
-    <div class="row">
+  		<div class="row">
 
-        <div class="col-sm-8 blog-main">
+  			<div class="col-sm-8 blog-main">
 
-			<div id="representation">
-				<blockquote>Problem is (follow me closely here, the science is pretty complicated), if I cut a hole in the Hab, the air won't stay inside anymore.</blockquote><br>
-				- Andy Weir, The Martian
-			</div>
-		</div>
-	</div>
+  				<div id="representation">
+  					<blockquote>Problem is (follow me closely here, the science is pretty complicated), if I cut a hole in the Hab, the air won't stay inside anymore.</blockquote><br>
+  					- Andy Weir, The Martian
+  				</div>
+  			</div>
+  		</div>
 
-	</div>
+  	</div>
 
 
-    <footer class="blog-footer">
-      <p>Blog template built for <a href="http://getbootstrap.com">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.</p>
-      <p>
-        <a href="#">Back to top</a>
-      </p>
-    </footer>
+  	<footer class="blog-footer">
+  		<p>Blog template built for <a href="http://getbootstrap.com">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.</p>
+  		<p>
+  			<a href="#">Back to top</a>
+  		</p>
+  	</footer>
 
 
     <!-- Bootstrap core JavaScript
     <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
     <script src="http://getbootstrap.com/assets/js/ie10-viewport-bug-workaround.js"></script>
 
-<script type="text/javascript">
-
-// This sample was inspired from the Redux and ngrx/core samples
-// which are discussed in this blog post:
-// http://onehungrymind.com/build-better-angular-2-application-redux-ngrx/
+    <!-- The file blog.js is the isomorphic part of the application -->
+    <script type="text/javascript" src="blog.js"></script>
 
-
-////////////////////////////////////////////////////////////////////////////////
-// Model 
-//
-const COUNTER_MAX = 10 ;
+    <script type="text/javascript">
 
-var model = {
-              posts: [
-                {
-                  id: 1,
-                  title: "The SAM Pattern",
-                  description: "SAM is a new reactive/functional pattern that simplifies Front-End architectures by clearly separating the business logic from the view and, in particular, strictly decoupling back-end APIs from the Front-End. SAM is technology independent and as such can be used to build Web Apps or Native Apps"
-                },
-                {
-                  id: 2,
-                  title: "Why I no longer use MVC Frameworks",
-                  description: "The worst part of my job these days is designing APIs for front-end developers. "
-                }
-              ],
-              itemId : 3 
-            } ;
+		// This sample was inspired from @rajaraodv
+		// https://medium.com/@rajaraodv/a-guide-for-building-a-react-redux-crud-app-7fe0b8943d0f#.e7501tdde
+
+		function present(data) {
+			// client side
+			//model.present(data) ;
 
-model.present = function(data,next) {
-    data = data || {} ;
-    
-    if (data.deletedItemId !== undefined) {
-        var d = -1 ;
-        model.posts.forEach(function(el,index) {
-            if (el.id !== undefined) {
-                if (el.id == data.deletedItemId) {
-                    d = index ;       
-                }
-            }
-        });
-        if (d>=0) {
-            model.lastDeleted = model.posts.splice(d,1)[0] ;
-        }
-    }
-    
-    if (data.lastEdited !== undefined) {
-        model.lastEdited = data.lastEdited ;
-    } else {
-        delete model.lastEdited ;
-    } 
-    
-    if (data.item !== undefined) {
-        if (data.item.id !== null) {
-            // has been edited
-            model.posts.forEach(function(el,index) {
-                if (el.id !== undefined) {
-                    if (el.id == data.item.id) {
-                        model.posts[index] = data.item ;       
-                    }
-                }
-            });
-            
-        } else {
-            // new item
-            data.item.id = model.itemId++ ;
-            model.posts.push(data.item) ;
-        }
-    }
-    
-    state.render(model,next) ;
-}
-
+			// server side
+			$.post( "http://107.170.242.211:5425/app/v1/present", data) 
+			.done(function( representation ) {
+				$( "#representation" ).html( representation );
+			}		
+			);
+		}
 
-////////////////////////////////////////////////////////////////////////////////
-// View
-//
-var view = {} ;
-
-// Initial State
-view.init = function(model) {
-    return view.ready(model) ;
-} ;
+		function init() {
+			// client side
+			//view.display(view.init(model)) ;
 
-// State representation of the ready state
-view.ready = function(model) { 
-    model.lastEdited = model.lastEdited || {} ;
-    var titleValue = model.lastEdited.title || 'Title' ;
-    var descriptionValue = model.lastEdited.description || 'Description' ;
-    var id = model.lastEdited.id || '' ;
-    var cancelButton = '<button id="cancel" onclick="JavaScript:return actions.cancel({});\">Cancel</button>\n' ;
-    var valAttr = "value" ;
-    var actionLabel = "Save" ;
-    var idElement = ', \'id\':\''+id+'\'' ;
-    if (id.length === 0) { cancelButton = '' ; valAttr = "placeholder"; idElement = "" ; actionLabel = "Add"}
-    var output = (
-            '<br><br><div class="blog-post">\n\
-               '+model.posts.map(function(e){
-                   return(
-                        '<br><br><h3 class="blog-post-title" onclick="JavaScript:return actions.edit({\'title\':\''+e.title+'\', \'description\':\''+e.description+'\', \'id\':\''+e.id+'\'});">'+e.title+'</h3>\n'
-                       +'<p class="blog-post-meta">'+e.description+'</p>'
-                       +'<button onclick="JavaScript:return actions.delete({\'id\':\''+e.id+'\'});">Delete</button>') ;
-                   }).join('\n')+'\n\
-             </div>\n\
-             <br><br>\n\
-             <div class="mdl-cell mdl-cell--6-col">\n\
-               <input id="title" type="text" class="form-control"  '+valAttr+'="'+titleValue+'"><br>\n\
-               <input id="description" type="textarea" class="form-control" '+valAttr+'="'+descriptionValue+'"><br>\n\
-               <button id="save" onclick="JavaScript:return actions.save({\'title\':document.getElementById(\'title\').value, \'description\': document.getElementById(\'description\').value'+idElement+'});">'+actionLabel+'</button>\n\
-               '+cancelButton+'\n\
-             </div><br><br>\n'
-        ) ;
-    return output ;
-} ;
-
-
-//display the state representation
-view.display = function(representation,next) {
-    var stateRepresentation = document.getElementById("representation");
-    stateRepresentation.innerHTML = representation;
-
-    // next(representation) ;
-} ;
+			// server side
+			$.get( "http://107.170.242.211:5425/app/v1/init", function( data ) {
+				$( "#representation" ).html( data );
+			}		
+			);
+		}
 
 
 
-////////////////////////////////////////////////////////////////////////////////
-// State
-//
-var state =  { view: view} ;
-
-model.state = state ;
-
-// Derive the state representation as a function of the systen
-// control state
-state.representation = function(model,next) {
-    var representation = 'oops... something went wrong, the system is in an invalid state' ;
-
-    if (state.ready(model)) {
-        representation = state.view.ready(model) ;
-    } 
-    
-    state.view.display(representation,next) ;
-} ;
-
-// Derive the current state of the system
-state.ready = function(model) {
-   return true ;
-} ;
-
-
-
-
-// Next action predicate, derives whether
-// the system is in a (control) state where
-// a new (next) action needs to be invoked
-
-state.nextAction = function(model) {} ;
-
-state.render = function(model,next) {
-    state.representation(model,next)
-    state.nextAction(model) ;
-} ;
+		// Display initial state
+		init() ;
 
 
-////////////////////////////////////////////////////////////////////////////////
-// Actions
-//
-
-function present(data) {
-	// client side
-	model.present(data) ;
-
-	// server side
-	// $.post( "http://localhost:5425/app/v1/present", data) 
-	// 	.done(function( representation ) {
-    //  				$( "#representation" ).html( representation );
-	// 		}		
-	// 	);
-}
-
-var actions = {} ;
-
-actions.edit = function(data) {
-	data.lastEdited = {title: data.title,  description: data.description, id: data.id } ;
-    present(data) ;
-    return false ;
-} ;
-
-actions.save = function(data) {
-    data.item = {title: data.title, description: data.description, id: data.id || null} ;
-    present(data) ;
-    return false ;
-} ;
-
-actions.delete = function(data) {
-    data = {deletedItemId: data.id} ;
-    present(data) ;
-    return false ;
-} ;
-
-actions.cancel = function(data) {
-    present(data) ;
-    return false ;
-} ;
-
-
-function init() {
-	view.display(view.init(model)) ;
-	// $.get( "http://localhost:5425/app/v1/init", function( data ) {
-    //  			$( "#representation" ).html( data );
-	// 	}		
-	// );
-}
-
-// Display initial state
-init() ;
-
-
-</script>
+	</script>
 </body>
 </html> 

File blog.js Added

  • Ignore whitespace
  • Hide word diff
+////////////////////////////////////////////////////////////////////////////////
+// Model 
+//
+const COUNTER_MAX = 10 ;
+
+var model = {
+              posts: [
+                {
+                  id: 1,
+                  title: "The SAM Pattern",
+                  description: "SAM is a new reactive/functional pattern that simplifies Front-End architectures by clearly separating the business logic from the view and, in particular, strictly decoupling back-end APIs from the Front-End. SAM is technology independent and as such can be used to build Web Apps or Native Apps"
+                },
+                {
+                  id: 2,
+                  title: "Why I no longer use MVC Frameworks",
+                  description: "The worst part of my job these days is designing APIs for front-end developers. "
+                }
+              ],
+              itemId : 3 
+            } ;
+
+model.present = function(data,next) {
+    data = data || {} ;
+    
+    if (data.deletedItemId !== undefined) {
+        var d = -1 ;
+        model.posts.forEach(function(el,index) {
+            if (el.id !== undefined) {
+                if (el.id == data.deletedItemId) {
+                    d = index ;       
+                }
+            }
+        });
+        if (d>=0) {
+            model.lastDeleted = model.posts.splice(d,1)[0] ;
+        }
+    }
+    
+    if (data.lastEdited !== undefined) {
+        model.lastEdited = data.lastEdited ;
+    } else {
+        delete model.lastEdited ;
+    } 
+    
+    if (data.item !== undefined) {
+        if (data.item.id !== null) {
+            // has been edited
+            model.posts.forEach(function(el,index) {
+                if (el.id !== undefined) {
+                    if (el.id == data.item.id) {
+                        model.posts[index] = data.item ;       
+                    }
+                }
+            });
+            
+        } else {
+            // new item
+            data.item.id = model.itemId++ ;
+            model.posts.push(data.item) ;
+        }
+    }
+    
+    state.render(model,next) ;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// View
+//
+var view = {} ;
+
+// Initial State
+view.init = function(model) {
+    return view.ready(model) ;
+} ;
+
+// State representation of the ready state
+view.ready = function(model) { 
+    model.lastEdited = model.lastEdited || {} ;
+    var titleValue = model.lastEdited.title || 'Title' ;
+    var descriptionValue = model.lastEdited.description || 'Description' ;
+    var id = model.lastEdited.id || '' ;
+    var cancelButton = '<button id="cancel" onclick="JavaScript:return actions.cancel({});\">Cancel</button>\n' ;
+    var valAttr = "value" ;
+    var actionLabel = "Save" ;
+    var idElement = ', \'id\':\''+id+'\'' ;
+    if (id.length === 0) { cancelButton = '' ; valAttr = "placeholder"; idElement = "" ; actionLabel = "Add"}
+    var output = (
+            '<br><br><div class="blog-post">\n\
+               '+model.posts.map(function(e){
+                   return(
+                        '<br><br><h3 class="blog-post-title" onclick="JavaScript:return actions.edit({\'title\':\''+e.title+'\', \'description\':\''+e.description+'\', \'id\':\''+e.id+'\'});">'+e.title+'</h3>\n'
+                       +'<p class="blog-post-meta">'+e.description+'</p>'
+                       +'<button onclick="JavaScript:return actions.delete({\'id\':\''+e.id+'\'});">Delete</button>') ;
+                   }).join('\n')+'\n\
+             </div>\n\
+             <br><br>\n\
+             <div class="mdl-cell mdl-cell--6-col">\n\
+               <input id="title" type="text" class="form-control"  '+valAttr+'="'+titleValue+'"><br>\n\
+               <input id="description" type="textarea" class="form-control" '+valAttr+'="'+descriptionValue+'"><br>\n\
+               <button id="save" onclick="JavaScript:return actions.save({\'title\':document.getElementById(\'title\').value, \'description\': document.getElementById(\'description\').value'+idElement+'});">'+actionLabel+'</button>\n\
+               '+cancelButton+'\n\
+             </div><br><br>\n'
+        ) ;
+    return output ;
+} ;
+
+
+//display the state representation
+view.display = function(representation,next) {
+    var stateRepresentation = document.getElementById("representation");
+    stateRepresentation.innerHTML = representation;
+
+    // next(representation) ;
+} ;
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// State
+//
+var state =  { view: view} ;
+
+model.state = state ;
+
+// Derive the state representation as a function of the systen
+// control state
+state.representation = function(model,next) {
+    var representation = 'oops... something went wrong, the system is in an invalid state' ;
+
+    if (state.ready(model)) {
+        representation = state.view.ready(model) ;
+    } 
+    
+    state.view.display(representation,next) ;
+} ;
+
+// Derive the current state of the system
+state.ready = function(model) {
+   return true ;
+} ;
+
+
+
+
+// Next action predicate, derives whether
+// the system is in a (control) state where
+// a new (next) action needs to be invoked
+
+state.nextAction = function(model) {} ;
+
+state.render = function(model,next) {
+    state.representation(model,next)
+    state.nextAction(model) ;
+} ;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Actions
+//
+
+
+var actions = {} ;
+
+actions.edit = function(data) {
+	data.lastEdited = {title: data.title,  description: data.description, id: data.id } ;
+    present(data) ;
+    return false ;
+} ;
+
+actions.save = function(data) {
+    data.item = {title: data.title, description: data.description, id: data.id || null} ;
+    present(data) ;
+    return false ;
+} ;
+
+actions.delete = function(data) {
+    data = {deletedItemId: data.id} ;
+    present(data) ;
+    return false ;
+} ;
+
+actions.cancel = function(data) {
+    present(data) ;
+    return false ;
+} ;

File server.js Modified

  • Ignore whitespace
  • Hide word diff
     res.status(200).send({authorized:false}) ;
 }) ;
 
-var model = {
-              posts: [
-                {
-                  id: 1,
-                  title: "The SAM Pattern",
-                  description: "SAM is a new reactive/functional pattern that simplifies Front-End architectures by clearly separating the business logic from the view and, in particular, strictly decoupling back-end APIs from the Front-End. SAM is technology independent and as such can be used to build Web Apps or Native Apps"
-                },
-                {
-                  id: 2,
-                  title: "Why I no longer use MVC Frameworks",
-                  description: "The worst part of my job these days is designing APIs for front-end developers. "
-                }
-              ],
-              itemId : 3 
-            } ;
 
-model.present = function(data,next) {
-    data = data || {} ;
-    
-    if (data.deletedItemId !== undefined) {
-        var d = -1 ;
-        model.posts.forEach(function(el,index) {
-            if (el.id !== undefined) {
-                if (el.id == data.deletedItemId) {
-                    d = index ;       
-                }
-            }
-        });
-        if (d>=0) {
-            model.lastDeleted = model.posts.splice(d,1)[0] ;
-        }
-    }
-    
-    if (data.lastEdited !== undefined) {
-        model.lastEdited = data.lastEdited ;
-    } else {
-        delete model.lastEdited ;
-    } 
-    
-    if (data.item !== undefined) {
-        
-        if (data.item.id !== "") {
-            // has been edited
-            model.posts.forEach(function(el,index) {
-                if (el.id !== undefined) {
-                    if (el.id == data.item.id) {
-                        model.posts[index] = data.item ;       
-                    }
-                }
-            });
-            
-        } else {
-            // new item
-            data.item.id = model.itemId++ ;
-            model.posts.push(data.item) ;
-        }
-    }
-    
-    state.render(model,next) ;
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// View
-//
-var view = {} ;
-
-// Initial State
-view.init = function(model) {
-    return view.ready(model) ;
-} ;
-
-// State representation of the ready state
-view.ready = function(model) { 
-    model.lastEdited = model.lastEdited || {} ;
-    var titleValue = model.lastEdited.title || 'Title' ;
-    var descriptionValue = model.lastEdited.description || 'Description' ;
-    var id = model.lastEdited.id || '' ;
-    var cancelButton = '<button id="cancel" onclick="JavaScript:return actions.cancel({});\">Cancel</button>\n' ;
-    var valAttr = "value" ;
-    var actionLabel = "Save" ;
-    var idElement = ', \'id\':\''+id+'\'' ;
-    if (id.length === 0) { cancelButton = '' ; valAttr = "placeholder"; idElement = "" ; actionLabel = "Add"}
-    var output = (
-            '<br><br><div class="blog-post">\n\
-               '+model.posts.map(function(e){
-                   return(
-                        '<br><br><h3 class="blog-post-title" onclick="JavaScript:return actions.edit({\'title\':\''+e.title+'\', \'description\':\''+e.description+'\', \'id\':\''+e.id+'\'});">'+e.title+'</h3>\n'
-                       +'<p class="blog-post-meta">'+e.description+'</p>'
-                       +'<button onclick="JavaScript:return actions.delete({\'id\':\''+e.id+'\'});">Delete</button>') ;
-                   }).join('\n')+'\n\
-             </div>\n\
-             <br><br>\n\
-             <div class="mdl-cell mdl-cell--6-col">\n\
-               <input id="title" type="text" class="form-control"  '+valAttr+'="'+titleValue+'"><br>\n\
-               <input id="description" type="textarea" class="form-control" '+valAttr+'="'+descriptionValue+'"><br>\n\
-               <button id="save" onclick="JavaScript:return actions.save({\'title\':document.getElementById(\'title\').value, \'description\': document.getElementById(\'description\').value'+idElement+'});">'+actionLabel+'</button>\n\
-               '+cancelButton+'\n\
-             </div><br><br>\n'
-        ) ;
-    return output ;
-} ;
-
-
-//display the state representation
-view.display = function(representation,next) {
-    next(representation) ;
-    //var stateRepresentation = document.getElementById("representation");
-    //stateRepresentation.innerHTML = representation;
-} ;
-
-
-////////////////////////////////////////////////////////////////////////////////
-// State
-//
-var state =  { view: view} ;
-
-model.state = state ;
-
-// Derive the state representation as a function of the systen
-// control state
-state.representation = function(model,next) {
-    var representation = 'oops... something went wrong, the system is in an invalid state' ;
-
-    if (state.ready(model)) {
-        representation = state.view.ready(model) ;
-    } 
-    
-    state.view.display(representation,next) ;
-} ;
-
-// Derive the current state of the system
-state.ready = function(model) {
-   return true ;
-} ;
-
-
-// Next action predicate, derives whether
-// the system is in a (control) state where
-// a new (next) action needs to be invoked
-
-state.nextAction = function(model) {} ;
-
-state.render = function(model,next) {
-    state.representation(model,next)
-    state.nextAction(model) ;
-} ;
+// The file blog.js is the isomorphic part of the application
+require('./blog.js') ;
 
 postman.addAPI(r, 'present', config.loginKey) ;
 app.post(apis.present,function(req,res) { 
Updated by Jean-Jacques Dubray

File blog.html Modified

  • Ignore whitespace
  • Hide word diff
     <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
     <meta name="description" content="">
     <meta name="author" content="">
-    <link rel="icon" href="../../favicon.ico">
 
-    <title>Blog Template for Bootstrap</title>
+    <title>Blog Template for Bootstrap Using the SAM Pattern</title>
 
     <!-- Bootstrap core CSS -->
     <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
               itemId : 3 
             } ;
 
-model.present = function(data) {
+model.present = function(data,next) {
     data = data || {} ;
     
     if (data.deletedItemId !== undefined) {
         }
     }
     
-    state.render(model) ;
+    state.render(model,next) ;
 }
 
 
 
 
 //display the state representation
-view.display = function(representation) {
+view.display = function(representation,next) {
     var stateRepresentation = document.getElementById("representation");
     stateRepresentation.innerHTML = representation;
+
+    // next(representation) ;
 } ;
 
-// Display initial state
-view.display(view.init(model)) ;
-
 
 
 ////////////////////////////////////////////////////////////////////////////////
 
 // Derive the state representation as a function of the systen
 // control state
-state.representation = function(model) {
+state.representation = function(model,next) {
     var representation = 'oops... something went wrong, the system is in an invalid state' ;
 
     if (state.ready(model)) {
         representation = state.view.ready(model) ;
     } 
     
-    state.view.display(representation) ;
+    state.view.display(representation,next) ;
 } ;
 
 // Derive the current state of the system
 
 state.nextAction = function(model) {} ;
 
-state.render = function(model) {
-    state.representation(model)
+state.render = function(model,next) {
+    state.representation(model,next)
     state.nextAction(model) ;
 } ;
 
 // Actions
 //
 
+function present(data) {
+	// client side
+	model.present(data) ;
+
+	// server side
+	// $.post( "http://localhost:5425/app/v1/present", data) 
+	// 	.done(function( representation ) {
+    //  				$( "#representation" ).html( representation );
+	// 		}		
+	// 	);
+}
+
 var actions = {} ;
 
-actions.edit = function(data, present) {
-    present = present || model.present ;
-    data.lastEdited = {title: data.title,  description: data.description, id: data.id } ;
+actions.edit = function(data) {
+	data.lastEdited = {title: data.title,  description: data.description, id: data.id } ;
     present(data) ;
     return false ;
 } ;
 
-actions.save = function(data, present) {
-    present = present || model.present ;
+actions.save = function(data) {
     data.item = {title: data.title, description: data.description, id: data.id || null} ;
     present(data) ;
     return false ;
 } ;
 
-actions.delete = function(data, present) {
-    present = present || model.present ;
+actions.delete = function(data) {
     data = {deletedItemId: data.id} ;
     present(data) ;
     return false ;
 } ;
 
-actions.cancel = function(data, present) {
-    present = present || model.present ;
+actions.cancel = function(data) {
     present(data) ;
     return false ;
 } ;
 
 
+function init() {
+	view.display(view.init(model)) ;
+	// $.get( "http://localhost:5425/app/v1/init", function( data ) {
+    //  			$( "#representation" ).html( data );
+	// 	}		
+	// );
+}
+
+// Display initial state
+init() ;
+
+
 </script>
 </body>
 </html> 
Updated by Jean-Jacques Dubray

File server.js Modified

  • Ignore whitespace
  • Hide word diff
-// postman.js can be found here https://bitbucket.org/jdubray/postman/src
+// 
 
 var express = require('express');
 var bodyParser = require('body-parser') ;
 app.post(apis.login,function(req,res) { 
     var username = req.body.username,
         password = req.body.password ;
-    console.log(req.body) ;
+    
     if (authnz.validateCredentials(username,password)) {
             console.log('Authorized') ;
             authnz.set(req,res,'authorized') ;
     } 
     
     if (data.item !== undefined) {
-        console.log(data.item.id) ;
+        
         if (data.item.id !== "") {
             // has been edited
             model.posts.forEach(function(el,index) {
 app.post(apis.present,function(req,res) { 
     var data = req.body ;
     model.present(data, function(representation) {
-        console.log(representation) ;
         res.status(200).send(representation) ;
     }) ;
 }) ;
Updated by Jean-Jacques Dubray

File snippet.txt Added

  • Ignore whitespace
  • Hide word diff
+{
+  "name": "sam-isomorphic blog-sample",
+  "version": "1.0.0",
+  "scripts": {
+    "start": "node server.js"
+  },
+  "dependencies": {
+    "body-parser": "*",
+    "cookie-parser": "",
+    "express": "4.13.x",
+    "morgan": "1.6.x"
+  },
+  "preferGlobal": false,
+  "private": true
+}
Updated by Jean-Jacques Dubray

File server.js Added

  • Ignore whitespace
  • Hide word diff
+// postman.js can be found here https://bitbucket.org/jdubray/postman/src
+
+var express = require('express');
+var bodyParser = require('body-parser') ;
+var morgan = require('morgan') ;
+var postman = require('./postman') ;
+
+
+var config = {} ;
+config.port = 5425 ;
+config.loginKey = 'abcdef0123456789' ;
+config.adminDirectory = './console/bower_components'
+config.username = 'sam' ;
+config.password = 'nomvc' ;
+
+var serverResponses = {
+    tooBusy: function(req,res) {
+        res.writeHead(429, { 'Content-Type': 'text/plain' });
+        res.end("Server is too busy, please try again later") ;
+    },
+    
+    unauthorized: function(req,res) {
+        res.header('Content-Type', 'text/html') ;
+        res.status(401).send('<htnl><body>Unauthorized access</body></html>') ;  
+    },
+    
+    serverError: function(req,res) {
+        res.header('Content-Type', 'text/html') ;
+        res.status(500).send('<htnl><body>Server Error</body></html>') ;  
+    }
+} ;
+
+var authnz = {
+
+    authorized: function (cookies) {
+        if (cookies.authorized > 0) {
+            return true ;
+        }
+        return false ;
+    },
+    
+    del: function(req, res, cookie) { 
+        if (cookie !== undefined) {
+            res.clearCookie(cookie);  
+        }
+    },
+    
+    set: function(req, res, cookie) { 
+        if (cookie !== undefined) {
+             res.cookie(cookie, +new Date(), { maxAge: 3600000, path: '/' }); 
+        }
+    },
+    
+    isSet: function(req, res, cookie) { 
+        if (cookie !== undefined) {
+             return res.cookies[cookie]; 
+        }
+    },
+    
+    validateCredentials: function(username,password) {
+        return ((username === config.username) && (password === config.password)) ;
+    }
+
+} ;
+
+var app = express();
+app.use(morgan('combined')) ;
+app.use(bodyParser.raw()) ;
+app.use(bodyParser.urlencoded({ extended: true }));
+
+var cookieParser = require('cookie-parser') ;
+app.use(cookieParser()) ;
+
+app.use(function(req, res, next) {
+  res.header("Access-Control-Allow-Origin", "*");
+  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
+  next();
+});
+
+app.use('/html', express.static(config.adminDirectory));
+
+
+var v = '/v1' ;
+var r = 'app' ;
+var a = 'api' ;
+var apis = {
+    login: '/'+r+v+'/login',
+    logout: '/'+r+v+'/logout',
+    present: '/'+r+v+'/present',
+    init: '/'+r+v+'/init'
+} ;
+
+
+var postman = require('./postman') ;
+
+postman.addAPI(r, 'login', config.loginKey) ;
+app.post(apis.login,function(req,res) { 
+    var username = req.body.username,
+        password = req.body.password ;
+    console.log(req.body) ;
+    if (authnz.validateCredentials(username,password)) {
+            console.log('Authorized') ;
+            authnz.set(req,res,'authorized') ;
+            res.status(200).send({authorized:true, user: {firstName:'Paul', lastName:'Smith', tel:'+1-425-555-1212'}}) ;
+    } else {
+        console.log('Unauthorized access') ;
+        res.status(200).send({authorized:false}) ;
+    }
+}) ;
+
+postman.addAPI(r, 'logout', config.loginKey) ;
+app.get(apis.logout,function(req,res) { 
+    authnz.del(req,res,'authorized') ;
+    res.status(200).send({authorized:false}) ;
+}) ;
+
+var model = {
+              posts: [
+                {
+                  id: 1,
+                  title: "The SAM Pattern",
+                  description: "SAM is a new reactive/functional pattern that simplifies Front-End architectures by clearly separating the business logic from the view and, in particular, strictly decoupling back-end APIs from the Front-End. SAM is technology independent and as such can be used to build Web Apps or Native Apps"
+                },
+                {
+                  id: 2,
+                  title: "Why I no longer use MVC Frameworks",
+                  description: "The worst part of my job these days is designing APIs for front-end developers. "
+                }
+              ],
+              itemId : 3 
+            } ;
+
+model.present = function(data,next) {
+    data = data || {} ;
+    
+    if (data.deletedItemId !== undefined) {
+        var d = -1 ;
+        model.posts.forEach(function(el,index) {
+            if (el.id !== undefined) {
+                if (el.id == data.deletedItemId) {
+                    d = index ;       
+                }
+            }
+        });
+        if (d>=0) {
+            model.lastDeleted = model.posts.splice(d,1)[0] ;
+        }
+    }
+    
+    if (data.lastEdited !== undefined) {
+        model.lastEdited = data.lastEdited ;
+    } else {
+        delete model.lastEdited ;
+    } 
+    
+    if (data.item !== undefined) {
+        console.log(data.item.id) ;
+        if (data.item.id !== "") {
+            // has been edited
+            model.posts.forEach(function(el,index) {
+                if (el.id !== undefined) {
+                    if (el.id == data.item.id) {
+                        model.posts[index] = data.item ;       
+                    }
+                }
+            });
+            
+        } else {
+            // new item
+            data.item.id = model.itemId++ ;
+            model.posts.push(data.item) ;
+        }
+    }
+    
+    state.render(model,next) ;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// View
+//
+var view = {} ;
+
+// Initial State
+view.init = function(model) {
+    return view.ready(model) ;
+} ;
+
+// State representation of the ready state
+view.ready = function(model) { 
+    model.lastEdited = model.lastEdited || {} ;
+    var titleValue = model.lastEdited.title || 'Title' ;
+    var descriptionValue = model.lastEdited.description || 'Description' ;
+    var id = model.lastEdited.id || '' ;
+    var cancelButton = '<button id="cancel" onclick="JavaScript:return actions.cancel({});\">Cancel</button>\n' ;
+    var valAttr = "value" ;
+    var actionLabel = "Save" ;
+    var idElement = ', \'id\':\''+id+'\'' ;
+    if (id.length === 0) { cancelButton = '' ; valAttr = "placeholder"; idElement = "" ; actionLabel = "Add"}
+    var output = (
+            '<br><br><div class="blog-post">\n\
+               '+model.posts.map(function(e){
+                   return(
+                        '<br><br><h3 class="blog-post-title" onclick="JavaScript:return actions.edit({\'title\':\''+e.title+'\', \'description\':\''+e.description+'\', \'id\':\''+e.id+'\'});">'+e.title+'</h3>\n'
+                       +'<p class="blog-post-meta">'+e.description+'</p>'
+                       +'<button onclick="JavaScript:return actions.delete({\'id\':\''+e.id+'\'});">Delete</button>') ;
+                   }).join('\n')+'\n\
+             </div>\n\
+             <br><br>\n\
+             <div class="mdl-cell mdl-cell--6-col">\n\
+               <input id="title" type="text" class="form-control"  '+valAttr+'="'+titleValue+'"><br>\n\
+               <input id="description" type="textarea" class="form-control" '+valAttr+'="'+descriptionValue+'"><br>\n\
+               <button id="save" onclick="JavaScript:return actions.save({\'title\':document.getElementById(\'title\').value, \'description\': document.getElementById(\'description\').value'+idElement+'});">'+actionLabel+'</button>\n\
+               '+cancelButton+'\n\
+             </div><br><br>\n'
+        ) ;
+    return output ;
+} ;
+
+
+//display the state representation
+view.display = function(representation,next) {
+    next(representation) ;
+    //var stateRepresentation = document.getElementById("representation");
+    //stateRepresentation.innerHTML = representation;
+} ;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// State
+//
+var state =  { view: view} ;
+
+model.state = state ;
+
+// Derive the state representation as a function of the systen
+// control state
+state.representation = function(model,next) {
+    var representation = 'oops... something went wrong, the system is in an invalid state' ;
+
+    if (state.ready(model)) {
+        representation = state.view.ready(model) ;
+    } 
+    
+    state.view.display(representation,next) ;
+} ;
+
+// Derive the current state of the system
+state.ready = function(model) {
+   return true ;
+} ;
+
+
+// Next action predicate, derives whether
+// the system is in a (control) state where
+// a new (next) action needs to be invoked
+
+state.nextAction = function(model) {} ;
+
+state.render = function(model,next) {
+    state.representation(model,next)
+    state.nextAction(model) ;
+} ;
+
+postman.addAPI(r, 'present', config.loginKey) ;
+app.post(apis.present,function(req,res) { 
+    var data = req.body ;
+    model.present(data, function(representation) {
+        console.log(representation) ;
+        res.status(200).send(representation) ;
+    }) ;
+}) ;
+
+
+postman.addAPI(r, 'init', config.loginKey) ;
+app.get(apis.init,function(req,res) { 
+    res.status(200).send(view.init(model)) ;
+}) ;
+
+
+
+app.listen(config.port, function() {
+    console.log("registering app on port: "+config.port) ;
+    //setTimeout(register(),2000) ; 
+});
  1. Prev
  2. 1
  3. 2
  4. Next