Jean-Jacques DubrayIsomorphic JavaScript with SAM | the Blog Sample

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121``` ``` SAM Sample | Blog Posts Blog Template for Bootstrap Using the SAM Pattern

The SAM Pattern Blog

The official blog sample impleting the SAM Pattern

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.

- Andy Weir, The Martian
```
 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186``` ```//////////////////////////////////////////////////////////////////////////////// // 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 = '\n' ; var valAttr = "value" ; var actionLabel = "Save" ; var idElement = ', \'id\':\''+id+'\'' ; if (id.length === 0) { cancelButton = '' ; valAttr = "placeholder"; idElement = "" ; actionLabel = "Add"} var output = ( '

\n\ '+model.posts.map(function(e){ return( '

'+e.title+'

\n' +'' +'') ; }).join('\n')+'\n\
\n\

\n\
\n\
\n\
\n\ \n\ '+cancelButton+'\n\

\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 ; } ; ```
 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140``` ```// 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('Unauthorized access') ; }, serverError: function(req,res) { res.header('Content-Type', 'text/html') ; res.status(500).send('Server Error') ; } } ; 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 ; 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}) ; }) ; // 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) { var data = req.body ; model.present(data, function(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 2 3 4 5 6 7 8 9 10 11 12 13 14 15``` ```{ "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 } ```