A brief history of JavaScript (90s, 00s, Prototype, jQuery, Backbone, TodoMVC, Virtual DOM, Node, Servers, Desktop)
The latest ECMAScript standards (ES6+). Discover native classes, promises for easy async, where promises fall short. How to compile ES6+ code today (Babel).
Short history of Node.js, it’s usages over time (servers, command line apps, robotics, desktop apps), things to know
Build tools and generators: Introduction to Grunt, Gulp, NPM Scripts, and Static Site Generators like DocPad
Browser innovations like WebRTC for webcam, microphone, and peer 2 peer communication - think video chat apps and netflix
Browser hinderances like the DOM and the introduction of the Virtual DOM (React.js) as well as creating native iOS applications using React’s Virtual DOM using React Native
Hi JavaScript (Netscape)!
Hi JScript (MS)!
Hi VBScript (MS)!
<script>
window.app = {}
app.validate = function () {
return (
document.getElementById('name').value
|| alert('Name is required!')
) && confirm('Are you sure?')
}
</script>
<form action="" method="post" onsubmit="return window.app.validate()">
<label for="name">Name:</label>
<input id="name" type="text" name="name">
<input type="submit" value="Delete the Internet">
</form>
Problems?
Would this cause Duplicate or Inconsistent Validation?
Solutions?
What if JavaScript could talk directly to the server?
// Request
var request = new XMLHttpRequest() // @QUESTION: What if this doesn't exist?
request.onreadystatechange = function () {
if ( this.readyState === (this.DONE || 4) ) {
try {
var result = JSON.parse(request.responseText)
}
catch (error) { // response was invalid json
throw new Error('Invalid response: ' + error.message)
}
if ( !result.success ) { // response had error
throw new Error(result.message || 'Unknown error.')
}
// success, handle result
}
}
request.open('POST', url, true)
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
request.send('data='+JSON.stringify(data))
Problems?
Will this work in other browsers?
Solutions?
Maybe we can create a compatibility script?
// Shim for IE Support
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") }
catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") }
catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP") }
catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.")
}
}
Problems?
Is this the only thing that needs a shim?
Solutions?
We have google, copy & paste, we'll be fine...
<script>
// ...
app.submit = function (form) {
if ( form.validated ) {
return true // continue with form submission
}
var name = document.getElementById('name').value
var data = {name: name}
var url = form.action || document.location.href
window.app.request(url, data, function (error, result) {
if ( error ) {
document.getElementById('error').innerHTML = error.message || error.toString()
form.validated = false // not validated, have the user try again
}
else {
form.validated = true
form.submit() // submit again, but this time it will continue through
}
})
return false // cancel form submission
}
</script>
<form action="" method="post" onsubmit="return window.app.submit(this)">
<div id="error"></div>
<label for="name">Name:</label>
<input id="name" type="text" name="name">
<input type="submit" value="Delete the Internet">
</form>
Problems?
Hrmm, this is starting to get complicated!
Solutions?
Maybe time for libraries.
// vanilla
var el = document.getElementById('myel')
if (el.classList) el.classList.add('pending')
else el.className += ' pending'
el.style.display = 'none'
// long-hand
var el = document.getElementById('myel')
Element.extend(myel)
el.addClassName('pending').hide()
// short-hand
$('myel').addClassName('pending').hide()
// however, prototype monkey patches, so this works
var el = document.getElementById('myel')
el.addClassName('pending').hide()
Problems?
Conflicts with native instances and classes?
Solutions?
Maybe we should provide new APIs?
AKA. Let's shim+awesomify the bundled APIs.
Also brought early classes.
// vanilla
var el = document.getElementById('myel')
if (el.classList) el.classList.add('pending')
else el.className += ' pending'
el.style.display = 'none'
// shorthand
$('myel').addClass('pending').hide()
// note, one monkey patch still exists
document.id('myel').addClass('pending').hide()
Problems?
Sometimes a lot of plumbing is needed...
Solutions?
What if there was a simpler way to extend things?
AKA. Let's shim+awesomify the new APIs.
Also brought early classes.
// vanilla
var el = document.getElementById('myel')
if (el.classList) el.classList.add('pending')
else el.className += ' pending'
el.style.display = 'none'
// long-hand
var el = document.getElementById('myel')
$(el).addClass('pending').hide()
// short-hand
$('#myel').addClass('pending').hide()
// easy chainable extensions
jQuery.prototype.yell = $.fn.yell = function () {
alert(this.innerHTML)
}
$('#myel').yell()
Problems?
Great for views, but what about managing state?
Solutions?
What state management options are there?
AKA. Let's shim+awesomify new APIs.
Problems?
How many loads occur? What about SEO? Compat?
Solutions?
Maybe we need browsers to step up here!
<div class="smarty">
{$myvar|default:"hi"|capitalize} <br/>
{assign var="myvar" value="hello"}
{$myvar|default:"hi"|capitalize}
</div>
<script>
$('.smarty').populate()
</script>
Problems?
Great for views, but what about data and actions?
Solutions?
Maybe we need to introduce Models and Controllers?
AKA. The server can do it, why can't we?
<div class="smarty">
Hi <br/>
Hello
</div>
=>
// Model
var Message = Backbone.Model.extend({})
var Messages = Backbone.Collection.extend({model: Message})
// View
var MessageList = Backbone.View.extend({
el: jQuery('.message-list').remove().first().prop('outerHTML'),
initialize: function () {
// listen to the collection to update the view
var messages = this.collection
messages.on('reset', this.render, this)
messages.on('add', this.addMessage, this)
messages.on('remove', this.removeMessage, this)
messsages.each(this.addMessage, this)
},
render: function () {
// render template, attach listeners, etc
},
addMessage: function () {
// render one message to the list
},
removeMessage: function () {
// remove one message from the list
}
})
// Controller
var messages = new Messages([new Message({text: 'hello')])
var messageList = new MessageList({collection: messages})
jQuery('.app').append(messageList.el)
Problems?
Is this the best way to do views? Seems complex.
Solutions?
What if there was a simpler way to extend things?
AKA. MVC for the Web.
Problems?
That's complicated... Can Browsers save us again?
Solutions?
Maybe templating and components can be native?
AKA. Let's try everything until we figure something out.
<dom-module id="contact-card">
<link rel="import" type="css" href="contact-card.css">
<template>
<content></content>
<iron-icon icon="star" hidden$="{{!starred}}"></iron-icon>
</template>
<script>
Polymer({
is: 'contact-card',
properties: {
starred: Boolean
}
});
</script>
</dom-module>
<contact-card starred>
<img src="profile.jpg" alt="Eric's photo">
<span>Eric Bidelman</span>
</contact-card>
Problems?
Is cross-browser support simple? Packaging? Easier?
Solutions?
Is it possible to remove the complexity in dev land?
AKA. Lets get browsers to make the DOM awesome.
var TodoList = React.createClass({
render: function () {
var createItem = function (item) {
return <li key={item.id}>{item.text}</li>
}
return <ul>{this.props.items.map(createItem)}</ul>
}
})
var TodoApp = React.createClass({
getInitialState: function () {
return {items: [], text: ''}
},
onChange: function (e) {
this.setState({text: e.target.value})
},
handleSubmit: function (e) {
e.preventDefault()
var nextItems = this.state.items.concat([{
text: this.state.text, id: Date.now()
}])
var nextText = ''
this.setState({items: nextItems, text: nextText})
},
render: function () {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
)
}
})
ReactDOM.render(<TodoApp />, mountNode)
Problems?
Well that's easy, what else can it do?
Solutions?
Well... everything...
AKA. Let's do it our way.
// iOS
var React = require('react-native')
var { TabBarIOS, NavigatorIOS } = React
var App = React.createClass({
render: function() {
return (
<TabBarIOS>
<TabBarIOS.Item title="React Native" selected={true}>
<NavigatorIOS initialRoute={{ title: 'React Native' }} />
</TabBarIOS.Item>
</TabBarIOS>
)
},
})
// Android
var React = require('react-native')
var { DrawerLayoutAndroid, ProgressBarAndroid } = React
var App = React.createClass({
render: function() {
return (
<DrawerLayoutAndroid
renderNavigationView={() => <Text>React Native</Text>}>
<ProgressBarAndroid />
</DrawerLayoutAndroid>
)
},
})
Problems?
So it can do everything! Do I need to know everything?
Solutions?
Well... maybe...
AKA. Native Everything!
var http = require('http'),
hostname = '127.0.0.1',
port = 1337,
file = 'file.txt'
http.createServer(function (req, res) { // AMAZING
res.writeHead(200, {'Content-Type': 'text/plain'})
res.write('Sending contents of: ' + file + '\n')
fs.createReadStream(file).pipe(res) // AMAZING
}).listen(port, hostname, function () {
console.log('Server running at http://' + hostname + ':' + port + '/');
})
Problems?
New platform, new foundations? How to share?
Solutions?
Well... downloading zip files won't cut it.
AKA. JavaScript for all the things.
# Publish
mkdir multiply
npm init
echo "module.exports = function (a, b) { return a*b }" > index.js
npm publish
# Consume
mkdir multiplier
npm init
npm install --save multiply
echo "console.log(require('multiply')(5, 10))" > index.js
node index.js
Problems?
That's awesome! What about libraries & apps?
Solutions?
JavaScript developers unite!
AKA. JavaScript packages for all the things.
<section id="editable" contenteditable="true">
<h2>Go ahead, edit away!</h2>
<p>Here's a typical paragraph element</p>
<ol>
<li>and now a list</li>
<li>with only</li>
<li>three items</li>
</ol>
</section>
Problems?
Standards? Compatibility? Selections? GUIs?
Solutions?
Quite messy right now. Many attempts at solving it.
// Server
// Create the HTTP server and serve our index.html
var server = require('http').createServer(function incoming(req, res) {
res.setHeader('Content-Type', 'text/html')
require('fs').createReadStream(__dirname + '/index.html').pipe(res)
})
// Attach Primus to the HTTP server.
var Primus = new (require('primus'))(server)
// Listen for connections and echo the events send.
primus.on('connection', function connection(spark) {
spark.on('data', function received(data) {
console.log(spark.id, 'received message:', data)
spark.write(data)
})
})
// Open server
server.listen(8080, function () {
console.log('Open http://localhost:8080 in your browser')
})
Problems?
Compatibility? Perhaps fallback to XHR?
Solutions?
All sorted by great libraries.
AKA. Proper Client <-> Server Communication
// Client
var outputEl = document.getElementById('output'),
echoEl = document.getElementById('echo')
// Tell primus to create a new default connection
var primus = new Primus()
// Listen for incoming data and log it in our textarea
primus.on('data', function received(data) {
outputEl.value += data + '\n'
})
// Listen for submits of the form so we can send messages
document.getElementById('write').onsubmit = function (e) {
if (e && e.preventDefault) e.preventDefault()
// Write the typed message
primus.write(echoEl.value)
echoEl.value = ''
}
<script>
navigator.mediaDevices.getUserMedia({audio:false, video:true}).then(function(stream) {
var videoTracks = stream.getVideoTracks()
stream.onended = function() {
console.log('Stream ended')
}
document.getElementById('webcam').srcObject = stream
})
</script>
<video id="webcam" autoplay></video>
Problems?
What about cross-browser?
Solutions?
Commitments from everyone except silent Apple.
AKA. P2P Media Streams.
// vanilla
function A (name) {
this.name = name || 'Unknown'
}
A.prototype.greet = function (greeting) {
alert(
(greeting || 'Hello') +
' ' + this.name
)
}
function B (name) {
A.call(this, name)
}
B.prototype = Object.create(A.prototype)
B.prototype.hi = function () {
this.greet('Hi')
}
var b = new B()
b.hi()
Problems?
Would still need to be compiled right? Compat?
Solutions?
Use babel for that.
AKA. Let's make JavaScript AWESOME!
// esnext
class A {
constructor (name) {
this.name = name || 'Unknown'
}
greet (greeting) {
alert(`${greeting || 'Hello'} ${this.name}`)
}
}
class B extends A {
hi () {
this.greet('Hi')
}
}
const b = new B()
b.hi()