with
Packaging, live reload from Webpack
Routing from monorouter
dispatching (with micro-architecture) from Flux
Components from React (options for Polymer, vanilla)
Much Thanks To
The Center For Open Science
Learn • Experience • Share
CVJS
Intermission
skunky
smoky
sour
spicy
spoiled
stagnant
stench
stinking
sulphur
sweaty
sweet
tart
tempting
vinegary
woody
yeasty
acidy
acrid
antiseptic
aromatic
balmy
biting
bitter
briny
burnt
citrusy
comforting
corky
damp
dank
distinctive
earthy
fishy
flowery
fragrant
fresh
fruity
gamy
gaseous
heavy
lemony
medicinal
metallic
mildewed
minty
moldy
musky
musty
odorless
peppery
perfumed
piney
pungent
putrid
reek
rose
rotten
savoury
scented
sharp
sickly
• Familiarize with VDOM
• Show one way binding
• Show a SPA workflow
• Show component integration
[Single Page Application]
We're Rich!?
It probably pays to look at the most popular 3 or 4 in each class before deciding on your js architecture. [large frameworks, functional, streaming, graphing, 3d, animation, reactive, jquery-like, eventing, packaging, dependency management, routing, history, dialects ...]
Clear starting point
A clear, but not enforced, standard way to do things
Explicitly clear separation of concerns, so we can mix and match and replace as needed
Easy dependency management
A way to use existing solutions so we don’t have to re-invent everything
A development workflow where we can switch from development mode to production with a simple boolean in a config.
http://blog.andyet.com/2014/08/13/opinionated-rundown-of-js-frameworks
from Aug 13, 2014 ・Henrik Joreteg
VDOM?
Internal Representation of all or part of a page
aTextbox = {
tag: 'input',
attrs: {type: 'text'},
children: []
}
function toHTMLElement(vdomElement){
attributes = _.pairs(vdomElement.attrs)
.reduce(
function(sum,KVArray){
return sum.concat([KVArray[0],'="',KVArray[1],'" '].join(""))
},"")
return ['<' + vdomElement.tag, attributes, '/>'].join(" ")
}
toHTMLElement(aTextbox) => <input type="text" />
React.js, mithril, mercury, ractive.js, jsonml
Christopher Chedeau http://calendar.perfplanet.com/2013/diff/
Christopher Chedeau http://calendar.perfplanet.com/2013/diff/
Christopher Chedeau http://calendar.perfplanet.com/2013/diff/
var webpack = require('webpack');
var port = JSON.parse(process.env.npm_package_config_port || 3000),
//subdomain = JSON.parse(process.env.npm_package_config_subdomain),
//url = subdomain ?
//'https://' + subdomain + '.localtunnel.me' :
url = 'http://localhost:' + port;
module.exports = {
// If it gets slow on your project, change to 'eval':
devtool: 'source-map',
entry: [
'webpack-dev-server/client?' + url,
'webpack/hot/only-dev-server',
'./web/js/app'
],
output: {
path: __dirname +"/web/js",
//path: __dirname +"/web",
filename: 'bundle.js',
publicPath: '/web/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
resolve: {
extensions: ['', '.js', '.jsx', '.cjsx', '.coffee']
},
module: {
loaders: [
{ test: /\.js$/, loaders: ['react-hot', 'jsx?harmony'] },
{ test: /\.css$/, loader: "style!css" },
{ test: /\.cjsx$/, loaders: ['react-hot', 'coffee', 'cjsx']},
{ test: /\.coffee$/, loader: 'coffee' }
]
}
};
{
"name": "cvjs demo",
"version": "0.1.2",
"description": "environment",
"scripts": {
"start": "node server.js"
},
"config": {
"port": 3000,
"subdomain": null
},
"repository": {
"type": "git",
"url": ""
},
"keywords": [
"react",
"reactjs",
"boilerplate",
"hot",
"hot",
"reload",
"hmr",
"live",
"edit",
"webpack"
],
"author": "Jon Tiemann <jtiemann@digitalpersonae.com>",
"license": "MIT",
"bugs": {
"url": "https://bitbucket.com/jtiemann/huh/issues"
},
"homepage": "",
"devDependencies": {
"react": "^0.12.0",
"jsx-loader": "~0.12.2",
"react-hot-loader": "^0.5.0",
"webpack": "^1.4.5",
"webpack-dev-server": "1.6.4",
"mcfly": "0.0.2",
"style-loader": "^0.8.2",
"css-loader": "^0.9.0",
"monorouter": "^0.7.1",
"monorouter-react": "^0.2.0",
"cjsx-loader": "^0.3.0",
"coffee-loader": "^0.7.2",
"coffee-react": "^1.0.2",
"coffee-script": "^1.8.0",
"gulp": "^3.8.8"
},
"dependencies": {
"react": "^0.12.0",
"flux": "^2.0.1",
"underscore": "^1.7.0",
"invariant": "^1.0.2",
"object-assign": "^1.0.0",
"jsx-loader": "~0.12.2",
"react-hot-loader": "~0.5.0",
"monorouter": "^0.7.1",
"monorouter-react": "^0.2.0",
"react-component-width-mixin": "^1.1.0"
},
"peerDependencies": {
"react": "*"
}
}
Flux
Reflux
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Dispatcher
* @typechecks
*/
"use strict";
var invariant = require('./invariant');
var _lastID = 1;
var _prefix = 'ID_';
function Dispatcher() {
this.$Dispatcher_callbacks = {};
this.$Dispatcher_isPending = {};
this.$Dispatcher_isHandled = {};
this.$Dispatcher_isDispatching = false;
this.$Dispatcher_pendingPayload = null;
}
/**
* Registers a callback to be invoked with every dispatched payload. Returns
* a token that can be used with `waitFor()`.
*
* @param {function} callback
* @return {string}
*/
Dispatcher.prototype.register=function(callback) {
var id = _prefix + _lastID++;
this.$Dispatcher_callbacks[id] = callback;
return id;
};
/**
* Removes a callback based on its token.
*
* @param {string} id
*/
Dispatcher.prototype.unregister=function(id) {
invariant(
this.$Dispatcher_callbacks[id],
'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
id
);
delete this.$Dispatcher_callbacks[id];
};
/**
* Waits for the callbacks specified to be invoked before continuing execution
* of the current callback. This method should only be used by a callback in
* response to a dispatched payload.
*
* @param {array<string>} ids
*/
Dispatcher.prototype.waitFor=function(ids) {
invariant(
this.$Dispatcher_isDispatching,
'Dispatcher.waitFor(...): Must be invoked while dispatching.'
);
for (var ii = 0; ii < ids.length; ii++) {
var id = ids[ii];
if (this.$Dispatcher_isPending[id]) {
invariant(
this.$Dispatcher_isHandled[id],
'Dispatcher.waitFor(...): Circular dependency detected while ' +
'waiting for `%s`.',
id
);
continue;
}
invariant(
this.$Dispatcher_callbacks[id],
'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
id
);
this.$Dispatcher_invokeCallback(id);
}
};
/**
* Dispatches a payload to all registered callbacks.
*
* @param {object} payload
*/
Dispatcher.prototype.dispatch=function(payload) {
invariant(
!this.$Dispatcher_isDispatching,
'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
);
this.$Dispatcher_startDispatching(payload);
try {
for (var id in this.$Dispatcher_callbacks) {
if (this.$Dispatcher_isPending[id]) {
continue;
}
this.$Dispatcher_invokeCallback(id);
}
} finally {
this.$Dispatcher_stopDispatching();
}
};
/**
* Is this Dispatcher currently dispatching.
*
* @return {boolean}
*/
Dispatcher.prototype.isDispatching=function() {
return this.$Dispatcher_isDispatching;
};
/**
* Call the callback stored with the given id. Also do some internal
* bookkeeping.
*
* @param {string} id
* @internal
*/
Dispatcher.prototype.$Dispatcher_invokeCallback=function(id) {
this.$Dispatcher_isPending[id] = true;
this.$Dispatcher_callbacks[id](this.$Dispatcher_pendingPayload);
this.$Dispatcher_isHandled[id] = true;
};
/**
* Set up bookkeeping needed when dispatching.
*
* @param {object} payload
* @internal
*/
Dispatcher.prototype.$Dispatcher_startDispatching=function(payload) {
for (var id in this.$Dispatcher_callbacks) {
this.$Dispatcher_isPending[id] = false;
this.$Dispatcher_isHandled[id] = false;
}
this.$Dispatcher_pendingPayload = payload;
this.$Dispatcher_isDispatching = true;
};
/**
* Clear bookkeeping used for dispatching.
*
* @internal
*/
Dispatcher.prototype.$Dispatcher_stopDispatching=function() {
this.$Dispatcher_pendingPayload = null;
this.$Dispatcher_isDispatching = false;
};
module.exports = Dispatcher;
https://github.com/kenwheeler/mcfly
/** McFly */
var Flux = new McFly();
/** Store */
_todos = [];
function addTodo(text){
_todos.push(text);
}
var TodoStore = Flux.createStore({
getTodos: function(){
return _todos;
}
}, function(payload){
if(payload.actionType === "ADD_TODO") {
addTodo(payload.text);
TodoStore.emitChange();
}
});
/** Actions */
var TodoActions = Flux.createActions({
addTodo: function(text){
return {
actionType: "ADD_TODO",
text: text
}
}
});
function getState(){
return {
todos: TodoStore.getTodos()
}
}
/** Controller View */
var TodosController = React.createClass({
mixins: [TodoStore.mixin],
getInitialState: function(){
return getState();
},
onChange: function() {
this.setState(getState());
},
render: function() {
return <Todos todos={this.state.todos} />;
}
});
/** Component */
var Todos = React.createClass({
addTodo: function(){
TodoActions.addTodo('test');
},
render: function() {
return (
<div className="todos_app">
<ul className="todos">
{ this.props.todos.map(function(todo, index){
return <li key={index}>Todo {index}</li>
})}
</ul>
<button onClick={this.addTodo}>Add Todo</button>
</div>
)
}
});
React.render(<TodosController />, document.body);
/** Boilerplate Flux Store */
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
/**
* @param {function} callback
*/
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
AppDispatcher.register(function(payload) {
var action = payload.action;
var text;
switch(action.actionType) {
case TodoConstants.TODO_CREATE:
text = action.text.trim();
if (text !== '') {
create(text);
}
break;
default:
return true;
}
/** Boilerplate Flux Action */
var TodoActions = {
/**
* @param {string} text
*/
create: function(text) {
AppDispatcher.handleViewAction({
actionType: TodoConstants.TODO_CREATE,
text: text
});
},
Less Flux Boilerplate Code
Cairngorm
/**
* ==========================================================
*
* This is not an official port of Cairngorm MVC Framework
* @author Babelium Project -> http://www.babeliumproject.com
*
* ==========================================================
*
* @source Cairngorm (Flex) Open Source Code:
* http://sourceforge.net/adobe/cairngorm/code/839/tree/cairngorm/trunk/frameworks/cairngorm/com/adobe/cairngorm/
* @source Simple Javascript Inheritance (./base.js):
* http://ejohn.org/blog/simple-javascript-inheritance/#postcomment
* @source Singleton Pattern w and w/o private members:
* http://stackoverflow.com/questions/1479319/simplest-cleanest-way-to-implement-singleton-in-javascript
*
*/
/* ============================================================
* control/FrontController.as
* ==========================================================*/
var Cairngorm = {};
Cairngorm.FrontController = Class.extend(
{
/**
* Constructor
*/
init : function ()
{
this.commands = {};
},
/**
* Add command
* @param evType = Event Type (String)
* @param commandRef = Class
*/
addCommand : function (evType, commandRef)
{
if ( evType == null )
return;
this.commands[evType] = commandRef;
Cairngorm.EventDispatcher.addEventListener(evType, this);
},
/**
* Remove Command
* @param commandName = String
*/
removeCommand : function ( commandName )
{
if ( commandName == null )
return;
this.commands[commandName] = null;
delete this.commands[commandName];
},
/**
* Execute Command
* @param ev = CairngormEvent
*/
executeCommand : function ( ev )
{
new this.commands[ev.type](ev.data).execute();
}
});
/* ============================================================
* control/CairngormEventDispatcher.as
* ==========================================================*/
Cairngorm.EventDispatcher = (function()
{
// Private interface
var _listeners = {};
// Public interface
return {
/**
* Add Event Listener
* @param type = Event Type (String)
* @param listener = function
*/
addEventListener : function ( type, listener )
{
if ( type != null && typeof listener.executeCommand == 'function' )
_listeners[type] = listener;
},
/**
* Dispatch event
* @param ev = Cairngorm Event
*/
dispatchEvent : function ( ev )
{
if ( _listeners[ev.type] != null )
_listeners[ev.type].executeCommand(ev);
}
};
})();
/* ============================================================
* control/CairngormEvent.as
* ==========================================================*/
Cairngorm.Event = Class.extend(
{
/**
* Constructor
* @param type = String
*/
init : function ( type, data )
{
this.type = type;
this.data = data != null ? data : {};
},
/**
* Dispatch Cairngorm Event
*/
dispatch : function ()
{
return Cairngorm.EventDispatcher.dispatchEvent(this);
}
});
/* ============================================================
* command/Command.as
* ==========================================================*/
Cairngorm.Command = Class.extend(
{
/**
* Constructor
*/
init : function ( data )
{
this.data = data;
},
/**
* Execute an action
*/
execute : function () {}
});
/* ============================================================
* business/HTTPServices.as
* ==========================================================*/
Cairngorm.HTTPServices = Class.extend(
{
/**
* Constructor
*/
init : function ()
{
this.services = {};
},
/**
* Finds a service by name
* @param name : service id
* @return RemoteObject
*/
getService : function ( name )
{
return this.services[name];
},
/**
* Register a service identified by its id
* @param name : service id
* @param service : httpservice
*/
registerService : function ( name, service )
{
this.services[name] = service;
}
});
/* ============================================================
* RemoteObject.as
* ==========================================================*/
Cairngorm.HTTPService = Class.extend(
{
/**
* Constructor
*/
init : function ( gateway, service )
{
this.target = gateway.target;
this.method = gateway.method;
this.service = service;
},
call : function ( params, responder )
{
if ( params == null )
params = "";
if ( this.method == "get" )
{
var src = this.target + this.service + "&" + params;
$.ajax(
{
// Target url
url : src,
// The success call back.
success : responder.onResult,
// The error handler.
error : responder.onFault
});
}
}
});
/* ============================================================
* business/ServiceLocator.as
* ==========================================================*/
Cairngorm.ServiceLocator = (function()
{
// Private interface
var _httpServices = new Cairngorm.HTTPServices();
// TODO var _remoteObjects = null;
// TODO var _webServices = null;
// Public interface
return {
/**
* Finds http service by name
* @return HTTPService
*/
getHttpService : function ( name )
{
return _httpServices.getService(name);
},
/**
* Register a service identified by its id
* @param name : service id
* @param service : HTTPService
*/
registerHttpService : function ( name, service )
{
_httpServices.registerService(name, service);
}
};
})();
/* ============================================================
* vo/ValueObject.as
* ==========================================================*/
Cairngorm.VO = Class.extend(
{
init : function (){},
/**
* Convert this object's properties
* to json object
*/
toJSON : function ()
{
var jsonObj = {};
for ( var i in this )
if ( typeof this[i] != "function" )
jsonObj[i] = this[i];
return jsonObj;
},
/**
* Convert this object's properties
* to json string
*/
toJSONStr : function ()
{
var jsonStr = "{";
for ( var i in this.toJSON() )
{
if ( jsonStr.length != 1 )
jsonStr += ",";
jsonStr += '"' + i + '": "' + this[i] + '"';
}
jsonStr += "}";
return jsonStr;
},
/**
* Convert this to base64
*/
toBase64 : function ()
{
return Base64.encode(this.toJSONStr());
}
});
//base.js
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
https://github.com/yahoo/flux-router-component
https://github.com/yahoo/flux-examples
https://github.com/yahoo/fetchr
https://github.com/yahoo/dispatchr
monorouter
==========
monorouter is an isomorphic JavaScript router by [@matthewwithanm] and
[@lettertwo]. It was designed for use with ReactJS but doesn't have any direct
dependencies on it and should be easily adaptable to other virtual DOM
libraries.
While it can be used for both browser-only and server-only routing, it was
designed from the ground up to be able to route apps on both sides of the wire.
**Note: This project is in beta and we consider the API in flux. Let us know if
you have any ideas for improvement!**
Usage
-----
Defining a router looks like this:
```javascript
var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');
monorouter()
.setup(reactRouting())
.route('/', function(req) {
this.render(MyView);
})
.route('/pets/:name/', function(req) {
this.render(PetView, {petName: req.params.name});
});
```
Here, we're simply rendering views for two different routes. With monorouter, a
"view" is any function that returns a DOM descriptor.
The router can be used on the server with express and [connect-monorouter]:
```javascript
var express = require('express');
var router = require('./path/to/my/router');
var monorouterMiddleware = require('connect-monorouter');
var app = express();
app.use(monorouterMiddleware(router));
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
```
And in the browser:
```javascript
router
.attach(document)
.captureClicks(); // This is optional—it uses the router to handle normal links.
```
See [the examples][monorouter examples] for a more in-depth look and more
tricks!
https://github.com/yahoo/flux-router-component
https://github.com/rackt/react-router
https://github.com/STRML/react-router-component
https://github.com/skevy/react-nested-router
https://gist.github.com/spoike/b9d6dddeb495c3e75477
better cheat sheet
Look at some code
/** @jsx React.DOM */
'use strict';
require("!style!css!../css/app.css");
var React = require('react');
var FluxCounterApp = require('./components/FluxCounterApp.react');
var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');
var PetList = require('../views/PetList');
var PetDetail = require('../views/PetDetail');
var Avatar = require('./components/Avatar');
var ContactMgr = require('./components/ContactMgr');
var Examples = require('./components/example');
monorouter()
.setup(reactRouting())
.route('/web/cvjs/', function(req){
this.render(function(){
return <div id="wrapper">
<div id="jt-meetup1"></div>
</div>
})
})
.route('index', '/web/', function(req) {
console.log("yo")
//this.render(PetList);
this.render(function(){
return <div>
<ContactMgr />
<FluxCounterApp />
<Examples />
<Avatar username="brigittrue" />
</div>
}
)
console.log("yo2")
})
.route('pet', '/web/pet/:name', function(req) {
//this.render(PetDetail, {petName: req.params.name});
this.render(function() {
return <div>
<ContactMgr />
<FluxCounterApp />
</div>
});
})
.attach(document.body)
.captureClicks();
//React.render(
// <FluxCounterApp />,
// document.getElementById('flux-counter')
//);
Packaging improving
One way data flow simplifies
components improving
ECMA 6
Thanks... and coming soon