Adding a Modern Twist to Legacy Web Applications
Process, Toolset and Buy In
by Jeff Dutra - @JeffDutraCanada
Housekeeping
All the code will be on GitHub https://github.com/jefferydutra/
AddingModernTwistToLegacyApps
Who Am I
o software developer/amateur skeptic/pretend psychologist
o Proficient Web Developer working towards becoming an Expert
Introduction
Manage dependencies and build your JavaScript with Node.js, Gulp, Browserify
Adding modern web functionality with React and Flux Strategies for Buy In to start using these toolsets now (or in some reasonable amount of time)
But Why?
Avoid misery of working with legacy code We will see how you can add independent and isolated
components to existing pages; pages that may be difficult to change
React and Flux allow you to make self-contained additions that handle their own data access/persistence
What is Gulp
• Grabs some files
• Modify them
• Output new files
A build system should only handle basic functionality and allow other libraries to do things they are made to do.
Why build with Gulp?
o Minify
o Concatenate
o JSHint
o Compile LESS or SASS
o Run your tests (now there is no excuse when it comes to writing tests)
Why not Grunt
o Code over configuration
o Grunt tries do everything itself
o Gulp relies on an eco-system of plug-ins
o Faster
o Cooler logo
The 4 functions Gulp provides
o gulp.task(name[, deps], fn)
o gulp.src(globs[, options])
o gulp.dest(path[, options])
o gulp.watch(glob[, opts], tasks)
globs are a pattern or an array of patterns for file matching. **/*.js = find all files that end in .js
JSHint Task
var gulp = require('gulp');var jshint = require('gulp-jshint');var stylish = require('jshint-stylish');var notify = require("gulp-notify");
gulp.task('jshint', function () { return gulp.src("./js/library/src/**/*.js") .pipe(jshint('.jshintrc')) .pipe(jshint.reporter(stylish)) .pipe(notify(function (file) { if (file.jshint.success) { // Don't show something if success return false; }
var errors = file.jshint.results.map(function (data) { if (data.error) { return "(" + data.error.line + ':' + data.error.character + ') ' + data.error.reason; } }).join("\n");
return file.relative + " (" + file.jshint.results.length + " errors)\n" + errors; }));
What is Browserify?
o Tool for compiling node-flavored commonjs modules for the browser
o Allows you to nicely organize your code
o Promotes code modularity
What are commonjs modules?
o Were created in the early days of server side JavaScript
o Three main variables:
o require
o exports
o module
CommonJs
var numberGreaterThanOrEqualTo = require('./numberGreaterThanOrEqualTo');
console.log(numberGreaterThanOrEqualTo(4,2));
Declaration of numberGreaterThanOrEqualTo.js
Usage of module
var numberGreaterThanOrEqualTo = function( value, testValue ){ if(isNaN(value)){ return false; } if(isNaN(testValue)){ return true; }
return Number(value) >= Number(testValue);};
module.exports = numberGreaterThanOrEqualTo;
Why React
o You can try it out incrementally
o Facebook/Instagram actually use it on their important products.
oAll about composition
Who is using it/migrating to it?
o Khan Academy
o AirBnB
o Yahoo mail
o Flipboard canvas
o Github (issue viewer)
o Atalassian HipChat rewrite
What is the Virtual DOM?
o Copy of the actual DOM
o When any change happens re-render everything to virtual DOM
o Has its own diff algorithm to learn what has changed
o Only update real DOM with changes only
JSX FTW!
var HelloMessage = React.createClass({ render: function() { return ( <div className='thisCanNotBeRight'> Hello {this.props.name} </div>); }});
React.render(<HelloMessage name="John" />, mountNode);
Think in React
o Break up your User Interface into hierarchical pieces
o Create a static version of your of your interface
o Stake out a basic representation of your state
o Decide where your state should live
State and Props
o Props are how you pass data to a child/owned component
o State is the internal state of module
oBoth trigger a re-render
State
• this.setState({
mykey: 'my value'
});
o var value = this.state.myKey;
o Should have one source of truth
Component Specs
oReactElement render()
o object getInitialState()
o object propTypes
o array mixins
o object statics
propTypes
propTypes: { // You can declare that a prop is a specific JS primitive. By default, these // are all optional. optionalArray: React.PropTypes.array, optionalString: React.PropTypes.string, optionalUnion: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, React.PropTypes.instanceOf(Message) ]),
// An array of a certain type optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number), optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number), optionalObjectWithShape: React.PropTyes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number }), requiredFunc: React.PropTypes.func.isRequired, customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error('Validation failed!'); } }
* When an invalid value is provided for a prop, a warning will be shown in the JavaScript console. Note that for performance reasons propTypes is only checked in development mode.
Component Specs
var React = require("React");var Router = require('react-router');
var Sample = React.createClass({ mixins: [ Router.Navigation, Router.State ], propTypes: { optionalString: React.PropTypes.string }, getInitialState: function() { return {optionalString: this.props.optionalString}; }, render: function(){ return ( <div className="row"> {this.state.optionalString} </div> ); }});
module.exports = Sample;
Lifecycle Methods
o componentWillMount
o componenetDidMount
o componentWillReceiveProps
o shouldComponentUpdate
o componentWillUpdate
o componentDidUpdate
o componentWillUnmount
Lifecycle Methods
componentWillMount
Invoked once, both on the client and server, immediately before the initial rendering occurs.
invoked once, only on the client (not on the server), immediately after the initial rendering occurs. At this point in the lifecycle, the component has a DOM representation which you can access via React.findDOMNode(this)
componentDidMount
What is Flux
Also brought to you by Facebook
Uni-directional data flow
Works great with React
More of a pattern, than a framework
Pub/Sub pattern
Dispatcher
o Singleton that is the central hub for an app
o When new data comes it propagates to all stores through callbacks
o Propagation triggered by dispatch()
Dispatcher
var Dispatcher = require('flux').Dispatcher;var assign = require('object-assign');var PayloadSources = require('../constants/PayloadSources');
function throwExceptionIfActionNotSpecified(action) { if (!action.type) { throw new Error('Action type was not provided'); }}
var AppDispatcher = assign(new Dispatcher(), { handleServerAction: function(action) { console.info('server action', action); throwExceptionIfActionNotSpecified(action); this.dispatch({ source: PayloadSources.SERVER_ACTION, action: action }); },
handleViewAction: function(action) { console.info('view action', action); throwExceptionIfActionNotSpecified(action); this.dispatch({ source: PayloadSources.VIEW_ACTION, action: action }); }});
module.exports = AppDispatcher;
ActionCreators
o a library of helper methods
o create the action object and pass the action to the dispatcher
o flow into the stores through the callbacks they define and register
ActionCreators
var AppDispatcher = require('../dispatcher/AppDispatcher');var CharacterApiUtils = require('../utils/CharacterApiUtils');var CharacterConstants = require('../constants/CharacterConstants');
var CharacterActions = {
receiveAll: function(characters) { AppDispatcher.handleServerAction({ type: CharacterConstants.ActionTypes.RECEIVE_CHARACTERS, characters: characters }); },
loadAll: function() { CharacterApiUtils.getCharacters(CharacterActions.receiveAll); }
};
module.exports = CharacterActions;
CharacterConstants
var ApiConstants = require('./ApiConstants');var keymirror = require('keymirror');
module.exports = { ApiEndPoints: { CHARACTER_GET: ApiConstants.API_ROOT + '/Character' }, ActionTypes: keymirror({ RECEIVE_CHARACTERS: null })};
CharacterApiUtils
var $ = require('jquery');var CharacterConstants = require('../constants/CharacterConstants');
var CharacterApiUtils = { getCharacters: function(successCallback) { $.get(CharacterConstants.ApiEndPoints.CHARACTER_GET) .done(function(data) { successCallback(data); }); }};
module.exports = CharacterApiUtils;
Stores
o Contain application state and logic
o Singleton
o Similar to MVC, except they manage state of more than one object
o Registers itself with the dispatcher through callbacks
o When updated, they broadcast a change event for views that are listening
Stores var AppDispatcher = require('../dispatcher/AppDispatcher');var EventEmitter = require('events').EventEmitter;var CharacterConstants = require('../constants/CharacterConstants');var assign = require('object-assign');var CHANGE_EVENT = 'change';var _characters = [];var CharacterStore = assign({}, EventEmitter.prototype, { init: function(characters) { characters.forEach(function(character) { _characters[character.id] = character; }, this); }, getAll: function() { return _characters; }, emitChange: function() { this.emit(CHANGE_EVENT); }, addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeChangeListener(CHANGE_EVENT, callback); }});AppDispatcher.register(function(payload) { var action = payload.action; switch (action.type) { case CharacterConstants.ActionTypes.RECEIVE_CHARACTERS: CharacterStore.init(action.characters); CharacterStore.emitChange(); break; }});module.exports = CharacterStore;
Implementation First Phase
1. Learn how to do this stuff on your own time
2. Start simple (JSHINT, JSCS)
3. Use Change management principles Up to you to explain what is in it for them
Implement React and Flux
If using ASP.NET MVC try React.Net firstUse on the next feature you work on (may require you
spending your own private time)Write a blog/wiki on the experience
Then let others have their input/concerns heard
Top Related