Building Non-shit APIs with JavaScript

Post on 19-May-2015

12.560 views 1 download

Tags:

description

As given at JSConf 2011.

Transcript of Building Non-shit APIs with JavaScript

Your JS LibraryDan Webb AKA @danwrong

Tuesday, May 3, 2011

Tuesday, May 3, 2011

In the beginning...

Tuesday, May 3, 2011

In the beginning...

Tuesday, May 3, 2011

$("p.surprise").addClass("ohmy").show("slow");

Tuesday, May 3, 2011

We all know what happened next...

jQuery

The Rest

Tuesday, May 3, 2011

Why?

Tuesday, May 3, 2011

Internals were not important...

Tuesday, May 3, 2011

...what had John created was a great interface

Tuesday, May 3, 2011

"All programmers are API designers"Joshua Bloch (http://lcsd05.cs.tamu.edu/slides/keynote.pdf)

Tuesday, May 3, 2011

The API is priority #1

Tuesday, May 3, 2011

❖ Predictability❖ Simplicity❖ Flexibility

Tuesday, May 3, 2011

Predictability

Tuesday, May 3, 2011

RTFMTuesday, May 3, 2011

Short attention spanTuesday, May 3, 2011

Think about your audience...

Tuesday, May 3, 2011

...use conventions people already know

Tuesday, May 3, 2011

Language conventions and standard library

Tuesday, May 3, 2011

THIS IS JAVASCRIPT

Tuesday, May 3, 2011

useCamelCase, yesReally.

Tuesday, May 3, 2011

Be careful with polyfills

Tuesday, May 3, 2011

Popular JS libraries

Tuesday, May 3, 2011

var paper = Raphael(10, 50, 320, 200);

var c = paper.circle(50, 40, 10);

c.attr("fill", "#f00");

c.show();

Tuesday, May 3, 2011

The problem domain

Tuesday, May 3, 2011

a.internal { color: #44e534; text-decoration: none;}

$('a.external').css({ color: '#44e534', textDecoration: 'none'});

Tuesday, May 3, 2011

Example: creating a DOM Builder

Tuesday, May 3, 2011

node.innerHTML = '<form method="post" action="/action">' + '<p>' + '<label>' + 'Username: <input type="text" name="username">' + '</label>' + '<label>' + 'Password: <input type="password" name="password">' + '</label>' + '</p>' + '</form>';

var form = document.createElement('form');var p = document.createElement('p');form.setAttribute('action', '/login');form.setAttribute('method', 'post');var usernameLabel = document.createElement('label');var usernameText = document.createTextElement('Username: ');var usernameInput = document.createElement('input');usernameInput.setAttribute('type', 'text');usernameInput.setAttribute('name', 'username');form.appendChild(p);p.appendChild(usernameLabel);// ... at this point I decided to give // all this up and become a farmer

Tuesday, May 3, 2011

var html = { element: function(name, attributes, children) { var node = document.createElement(name); for (var attr in attributes) { node.setAttribute(attr, attributes[attr]); } for (var i=0, len=children.length; i < len; i++) { node.appendChild(children[i]); } return node; }}

Tuesday, May 3, 2011

var form = html.element( 'form', { action: '/login', method: 'post' } [ html.element('p', {}, [ html.element('label', {}, [ document.createTextElement('Username: '), html.element('input', { type: 'text', name: 'username' }, []), // ... you get the idea ]) ]) ]);

Tuesday, May 3, 2011

var form = html.form({ action: '/login', method: 'post' }, [ html.p({}, [ html.label({}, [ document.createTextElement('Username: '), html.input({ type: 'text', name: 'username' }, []), // ... you still get the idea, right? ]) ]) ]);

Tuesday, May 3, 2011

function elementBuilder(name) { return function(attributes, children) { return html.element(name, attributes, children); }}

function generateBuilderFunctions(elements) { for (var i=0, len=elements.length; i < len; i++) { html[elements[i]] = createElementBuilder(elements[i]); }}

generateBuilderFunctions("p|div|span|strong|em|img|table|tr|td|th|thead|tbody|tfoot|pre|code|h1|h2|h3|h4|h5|h6|ul|ol|li|form|input|textarea|legend|fieldset|select|option|blockquote|cite|br|hr|dd|dl|dt|address|a|button|abbr|acronym|script|link|style|bdo|ins|del|object|param|col|colgroup|optgroup|caption|label|dfn|kbd|samp|var".split("|"));

Tuesday, May 3, 2011

Simplicity

Tuesday, May 3, 2011

Tuesday, May 3, 2011

Don’t make me RTFM again...

Tuesday, May 3, 2011

Sensible defaults

Tuesday, May 3, 2011

Tuesday, May 3, 2011

Tuesday, May 3, 2011

var evt = document.createEvent("MouseEvents");

evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);

Tuesday, May 3, 2011

element.addEventListener('input', function() { // do some front-end magic}, false);

Tuesday, May 3, 2011

var evt = document.createEvent("MouseEvents");

evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);

Tuesday, May 3, 2011

Use options hashes for optional arguments

Tuesday, May 3, 2011

evt.initMouseEvent("click", { bubble: false, relatedTarget: thing});

Tuesday, May 3, 2011

Function calls should read well

Tuesday, May 3, 2011

// replace oldNode with newNode - DOM API oldNode.parentNode.replaceChild(newNode, oldNode);

// Dojo dojo.place(newNode, oldNode, "replace");

// jQuery$(oldNode).replaceWith(newNode);

Tuesday, May 3, 2011

Mask complexity if possible

Tuesday, May 3, 2011

var con = xd.connect({ src: 'http://danwebb.net/receiver' });

con.bind('ready', function() { rpc(con).call('getData', function(result) { alert(result); });});

Tuesday, May 3, 2011

xd.connect({ src: 'http://danwebb.net/receiver' }, function(con) { rpc(con).call('getData', function(result) { alert(result); });});

Tuesday, May 3, 2011

var con = xd.connect({ src: 'http://danwebb.net/receiver' });

rpc(con).call('getData', function(result) { alert(result);});

Tuesday, May 3, 2011

Back to the DOM Builder

Tuesday, May 3, 2011

var form = html.form({ action: '/login', method: 'post' }, [ html.p({}, [ html.label({}, [ document.createTextElement('Username: '), html.input({ type: 'text', name: 'username' }, []), // ... you still get the idea, right? ]) ]) ]);

Tuesday, May 3, 2011

var form = html.form({ method: 'post', action: '/login' }, html.p( html.label( 'Username: ', html.input({ type: 'text', name: 'username' }) ), html.label( 'Password: ', html.input({ type: 'password', name: 'pass' }) ), html.input({ type: 'submit', value: 'Login'}) ));

Tuesday, May 3, 2011

function elementBuilder(name) { return function() { var attributes = {}, children = [], args = Array.prototype.slice.call(arguments);

// if the first arg is not a element or a string then its an attributes hash if (!args[0].nodeType && typeof args[0] != 'string') { attributes = args.unshift(); } // children can be an array or remaining args if (Array.isArray(args[0])) { args = args[0]; }

// add rest of args as children converting any strings to text nodes for (var i=0, len=args.length; i < len; i++) { if (typeof args[i] == 'string') { children.push(document.createTextNode(args[i])); } else { children.push(args[i]); } }

return html.element(name, attributes, children); }}

Tuesday, May 3, 2011

Flexibility

Tuesday, May 3, 2011

Tuesday, May 3, 2011

Tuesday, May 3, 2011

Tuesday, May 3, 2011

Remember: you can't please everyone

Tuesday, May 3, 2011

Don’t try to second guess every use case

Tuesday, May 3, 2011

Options hashes != flexibility

Tuesday, May 3, 2011

28 options!Tuesday, May 3, 2011

Add hackability

Tuesday, May 3, 2011

public, internal, protected

Tuesday, May 3, 2011

var lib = (function() { var private = function() { // you can't mess with me };

return { _internal: function() { // you probably shouldn't mess with me }, public: function() { // I'm part of the API - call me sometime } }}());

Tuesday, May 3, 2011

...using functions

Tuesday, May 3, 2011

// https://github.com/ender-js/Ender

$._select = function(selector, root) { return Sizzle(selector, root);}

$._select = function (selector, root) { return (root || document).querySelectorAll(selector);});

Tuesday, May 3, 2011

..inheritance

Tuesday, May 3, 2011

// github.com/danwrong/loadrunner

function Mustache(path) { this.path = path;}Mustache.prototype = new loadrunner.Dependency;Mustache.prototype.start = function() { var me = this; $.get(this.path, function(result) { var template = Mustache.parse(result); me.complete(template); });}

using(new Mustache('thing.mustache'), function(template) { template.evaluate({ a: 'yep', b: 'nope' });});

Tuesday, May 3, 2011

..duck typing

Tuesday, May 3, 2011

Meanwhile, back in DOM Builder land...

Tuesday, May 3, 2011

function elementBuilder(name) { return function() { // code collapsed for clarity

// add rest of args as children // converting any strings to text nodes for (var i=0, len=args.length; i < len; i++) { if (typeof args[i] == 'string') { node = document.createTextNode(args[i]); } else { if (typeof args[i].toDOM == 'function') { node = args[i].toDOM(); } else { node = args[i]; } }

children.push(node); }

return html.element(name, attributes, children); }}

Tuesday, May 3, 2011

function Tweet(text, author, timestamp) { this.text = text; this.author = author; this.timestamp = timestamp;}Tweet.prototype.toDOM = function() { return html.p( { 'class': 'tweet' }, html.strong(this.author.name), this.text, html.span({ 'class': 'time' }, this.timestamp) );}

var timeline = // an array of Tweet objects

document.appendChild(html.div({ id: 'timeline' }, timeline));

Tuesday, May 3, 2011

Create a framework for solving your problem...

Tuesday, May 3, 2011

...then build your library on top of that

Tuesday, May 3, 2011

You got yourself a plugin system

Tuesday, May 3, 2011

Build tools to solve the problem

Tuesday, May 3, 2011

❖ Design up front❖ Make use of conventions❖ Don’t make me think❖ Build in hackability

Tuesday, May 3, 2011

Questions?@danwrong

Tuesday, May 3, 2011

@jointheflocktwitter.com/jobsdan@twitter.com

Tuesday, May 3, 2011

Eye of the BeholderTuesday, May 3, 2011

ChainingTuesday, May 3, 2011