Incoming...
Get Data. Use "promises" to manage code organization and timing of get/posts. [see Rx.js for complex event management]
Shape
Arrays, hashes and/or sets but if it starts getting complicated...
Have a look at Mori (or LoDash)
mori.js is a port of some of the structures and functions of clojurescript.
- Efficient immutable data structures - no cloning required
- Uniform iteration for all types
- Value based equality
Code Sample
var keys = ["foo", "bar", "baz"];
var vals = [1, 2, 3];
var h = mori.zipmap(keys, vals); // => {"foo" 1, "bar" 2, "baz" 3}
https://github.com/swannodette/mori
http://swannodette.github.io/mori/
Transform
Need DOM? transform your internal data structure(s) for rendering.
//helper function
jtiles.r_nth = function(num, arr){
return m.nth(arr, num)
}
jtiles.build_world = (function () {
//return m.clj_to_js(
return m.map(function (x) {
return m.map(function (y) {
return {'cell': x + ':' + y }
}, m.range(dim)
)
}, m.range(dim)
)
}());
jtiles.build_board = function () {
return m.reduce(function (sum, unit) {
return sum.concat('<div class="cell" data-x="' + Math.floor(unit / dim) + '" unit="" dim=""></div>')
}, [], m.range(dim * dim));
}
jtiles.build_json_tile = function (item) {
return '<li class="ui-state-default">' + item + '</li>'
}
jtiles.update_json_tile = function (arr, s) {
if ($('[data-x="' + arr[0] + '"][data-y="' + arr[1] + '"]').children().length > 0 ) {
$('[data-x="' + arr[0] + '"][data-y="' + arr[1] + '"]').children().remove()
}
return $('[data-x="' + arr[0] + '"][data-y="' + arr[1] + '"]').append(jtiles.build_json_tile(s))
}
jtiles.update_json_cell = function (x, y, s) {
return m.comp(m.partial(jtiles.r_nth, y), m.partial(jtiles.r_nth, x))(jtiles.json_world).cell = s
}
// End Grid Snippets
//-------------------------------------------------------
// Begin Validation Snippet
//VALIDATION ADD-IN
;(function(mtms, $, undefined) {
mtms.validate = function(valType) {
return valType();
}
mtms.validateAll = function(fields, displayFn) { //send only 1 parameter for no visual display of errors
//ADD VALIDATION TYPES HERE AND IN RULES BELOW
var VALTYPES = {"required": mtms.required, "email-format": mtms.emailFormat, "cc-format": mtms.ccValidator},
allGood = true
$.map(fields, function(field, index) {
$.map(field.getAttribute('data-validate').split(/[ ,]+/).filter(function(vunit){return vunit !== ""}), function(valType, indx) {
if (!mtms.validate(VALTYPES[valType])(field)) {
allGood = false;
typeof displayFn !== "undefined" ? $(field).addClass('error') : null;
typeof displayFn !== "undefined" ? mtms[displayFn](field, valType) : null ;
}
});
})
return allGood;
}
mtms.growl = function(field, valType){
$.growl.error({duration: 4000,
message: field.getAttribute('class').replace('error', '').split("-").join(" ") +
" has " + mtms.aAn(valtype) + ' ' + valtype.split('-').join(' ') + ' issue' });
}
// VALIDATION RULES
mtms.required = function() {
return function(self) {
if (self.value === "") {
return false;
}
else {
return true;
}
}
}
mtms.emailFormat = function() {
var pattern = /^(\w|')+([\.-]?(\w|')+)*@\w+([\.-]?\w+)*(\.\w{2,6})+$/;
return function(self) {
if (!pattern.test(self.value)) {
return false;
}
else {
return true;
}
}
}
// Variant of Avraham Plotnitzky's String.prototype method mixed with the "fast" version
// see: https://sites.google.com/site/abapexamples/javascript/luhn-validation
mtms.ccValidator = function(){
return function luhnChk(self) {
var luhn = self.value,
len = luhn.length,
mul = 0,
prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
sum = 0;
while (len--) {
sum += prodArr[mul][parseInt(luhn.charAt(len), 10)];
mul ^= 1; //shorthand for mul = mul ^ 1, bitwise XOR with 1 so it flips the 0 bit, ie 0 => 1 or 1=> 0 on 0 bit 6 => 1 [110 => 111]
}
return sum % 10 === 0 && sum > 0;
};
}
//END VALIDATION CODE
//EVENT WITH VALIDATE (SPECIFIC VALIDATOR)
$('#cc-number').on("blur", function(){
if (!mtms.validate(mtms.ccValidator)(this)) {
$(this).addClass('error');
$.growl.error({message: mtms.errorText(this.id) });
}
else {
$(this).removeClass('error');
}
})
//END EVENT WITH VALIDATE (SPECIFIC VALIDATOR, SPECIFIC ELEMENT)
//BEGIN EVENT WITH VALIDATE OF (ALL VALIDATORS OF ARRAY OF ELEMENTS)
//if valid Address, ajax cart/pricing shippingOptions
if (mtms.validateAll($('#shipping-panel').children('[data-validate]'))){
mtms.getShippingCartPricing(); //deals with data and visualization
}
else{
//reset shipping-method drop dpwn
$('#shipping-method').html("<option>please enter shipping address</option>");
//TODO: reset price?
}
//END EVENT WITH VALIDATE OF (ALL VALIDATORS OF ARRAY OF ELEMENTS)
//BEGIN TWO-WAY BINDING [THE EASY WAY] ie8+
function bindModelInput(obj, property, domElem) {
Object.defineProperty(obj, property, {
get: function() { return domElem.value; },
set: function(newValue) { domElem.value = newValue; },
configurable: true
});
}
input1.onchange = function() { user.name = user.name } //sync both inputs.
//<input id="foo">
user = {}
bindModelInput(user,'name',document.getElementById('foo')); //hey presto, we now have two-way data binding
//END TWO_WAY BINDING
Transform
Need DOM? Transform your internal data structure(s) for rendering. render(state) fn
<widget class="widget"></widget>
/*jshint multistr: true */
//document.createElement('widget');
/* ------------------ BEGIN STAR RATER ------------------------*/
(function (stars, $) {
stars.markup = '\
<div class="ratingdiv">\
<a href="#" class="undone" rel="1">star one</a>\
<a href="#" class="undone" rel="2">star two</a>\
<a href="#" class="undone" rel="3">star three</a>\
<a href="#" class="undone" rel="4">star four</a>\
<a href="#" class="undone" rel="5">star five</a>\
</div>';
stars.css = document.createElement("style");
stars.css.type = "text/css";
stars.css.innerHTML = ' \
.ratingdiv{overflow:hidden; width:150px;display:block; margin-top: 15px; margin-left:auto; margin-right:auto} \
.ratingdiv .undone, .ratingdiv .fade{\
background:url("http://localhost/~tbone/star.gif") -30px 0 no-repeat;\
width:28px;\
display:block;\
height:30px;\
float:left;\
margin-right:2px;\
text-indent:-99999px\
}\
.ratingdiv .done{\
background:url("http://localhost/~tbone/star.gif") no-repeat;\
width:28px;\
display:block;\
height:30px;\
float:left;\
margin-right:2px;\
text-indent:-99999px\
}\
.ratingdiv .undone:hover{background-position:0}';
document.head.appendChild(stars.css);
}(window.stars = window.stars || {}, jQuery));
$(document).ready(function(){
$('star').append(stars.markup);
$('.ratingdiv .undone').click(function(){
var div = '.ratingdiv';
var rating = $(this).attr('rel');
var rater = $(this).parent().children().map(function(index, unit){ unit.className = rating >= $(unit).attr('rel') ? 'done' : 'fade'; return unit;});
$(div, this).append(rater);
});
});
/* ------------------ BEGIN DYSLEXIFIER ------------------------*/
(function(mw, $) {
//Helpers
Array.prototype.shuffle = function() {
//work on the copy, original unchanged
var array = this.slice(0);
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
//Structures
mw.theSelector = ['cool', 'cruel', 'interesting'];
mw.markup2 = '<div class="wrapper"> \
<button class="about">the Dyslexifier</button> \
<select class="states"></select> \
<textarea class="in"></textarea> \
<label class="reverse"></label> \
<textarea class="dyslexic"></textarea> \
<star></star>
</div>';
//DOM Structures
mw.markup = (function(){
//var wrapper = document.createElement("div");
//wrapper.id = "wrapper";
//wrapper.className = "wrapper";
//var theButton = document.createElement("button");
//theButton.innerHTML = "the Dyslexifier";
//var theSelector = document.createElement("select");
//theSelector.classList.add("states");
//var theIn = document.createElement("textarea");
//theIn.id = "in";
//theIn.className = "in";
//var theReverse = document.createElement("label");
//theReverse.id = "reverse";
//theReverse.className = "reverse";
//var dyslexic = document.createElement("label");
//dyslexic.id = "dyslexic";
//dyslexic.className = "dyslexic";
//var star = document.createElement("star");
//DOM Event, should further segregate by moving to area of events in $(document).ready...
//theButton.onclick= function(evt){alert("Welcome to the Dyslexifier (" + "dyslexifier".split("").shuffle().join("") + ")!");};
//return [wrapper, theButton, theSelector, theIn, theReverse, dyslexic, star];
return ['<div class="wrapper"></div>',
'<button class="about">the Dyslexifier</button>',
'<select class="states"></select>',
'<textarea class="in"></textarea>',
'<label class="reverse"></label>',
'<label class="dyslexic"></label>',
'<star></star>'];
}());
mw.css = document.createElement("style");
mw.css.type = "text/css";
mw.css.innerHTML = 'widget .wrapper { width:25%; min-width:150px; \
margin-left:auto; \
margin-right:auto; \
border:solid} \
widget button { width:100%; \
display: block; margin-left:auto; \
margin-right:auto; \
color:green} \
widget label {margin: 0 0 0 5px; border-bottom: 1px solid;} \
widget textarea, widget select { width:90%; min-width:140px; \
display: block; \
margin-left: auto; \
margin-right: auto; margin-bottom: 10px;}\
widget select {visibility: hidden; }\
widget textarea {height: 70px;}';
//INITIAL RENDER
document.head.appendChild(mw.css);
//for (var i=0; i < $("widget").length; i++){
var el = "widget";
var elArray = $(el);
// if using arrays of HTMLElements ie document.create...
/* mw.markup.map(function(unit, index){
return index !== 0 ? $.map(elArray, function(els, index){$(els).children().first().append($(unit));}) : $.map(elArray, function(els, index){$(els).html($(unit));});
}); */
// if using arrays of strings ie '['<p></p>',...]'
/* mw.markup.map(function(unit, index){
return index !== 0 ? $.map(elArray, function(els, index){$(els).children().first().append(unit);}) : $.map(elArray, function(els, index){$(els).html(unit);});
}); */
//if using plain old markup
$('widget').html(mw.markup2);
$('widget').on("click", '.about', function(evt){
alert("Welcome to the Dyslexifier (" + "dyslexifier".split("").shuffle().join("") + ")!");
});
//}
})(window.mw = window.mw || {}, jQuery);
$(document).ready(function(){
//$('body').append(stars.markup);
//$('widget').html(mw.markup);
$('.states').html(mw.theSelector.reduce(function(sum, unit){return sum.concat('<option>' + unit + '</option>');}, ""));
//EVENTS
/*
$('body').on("keyup", '#in', function(evt){
$('#reverse').text('backwards: '+evt.currentTarget.value.split(" ").reverse().join(" ").split("").reverse().join(""));
});
*/
// $('#states').prop("selectedIndex") or document.getElementById("states").selectedIndex
$('widget').on("keyup", '.in', function(evt){
$(this).siblings('.dyslexic').html(evt.currentTarget.value.split(" ").map(function(unit){return unit.split("").shuffle().join("");}).join(" "));
});
});
Simple Widget
document.body.innerHTML = ""
b = document.createElement("button")
s = document.createElement("select")
d = ["jon", "ed", "juanita", "edwina"]
b.onclick= function(evt){alert("I've been clicked!")}
document.body.appendChild(b)
document.body.appendChild(s)
b.innerHTML="Click Me"
s.innerHTML = d.reduce(function(sum, unit){return sum.concat("<option>" + unit + "</option>")}, "")
document.getElementsByTagName("select")[0].options[document.getElementsByTagName("select")[0].selectedIndex].text
b.onclick= function(evt){alert(document.getElementsByTagName("select")[0].options[document.getElementsByTagName("select")[0].selectedIndex].text + ", I've been clicked!")}
//s = function (evt){alert("I've changed!"); document.body.getElementsByTagName("button")[0].dispatchEvent()}
myEvent = new MouseEvent('click', {'view': window,'bubbles': true, 'cancelable': true });
s.onchange = function (evt){alert("I've changed!"); document.body.getElementsByTagName("button")[0].dispatchEvent(myEvent)}
s.innerHTML = d.reduceRight(function(sum, unit){return sum.concat("<option>" + unit + "</option>")}, "")
dv = document.createElement('div');
dv.setAttribute('class', 'container');
j = document.body.innerHTML
document.body.innerHTML = ""
document.body.appendChild(dv)
dv.innerHTML = j
//events are gone!, spo are connections to variables
document.getElementsByTagName("select")[0].onchange = function (evt){alert("I've changed!"); document.body.getElementsByTagName("button")[0].dispatchEvent(myEvent)}
document.getElementsByTagName("button")[0].onclick= function(evt){alert(document.getElementsByTagName("select")[0].options[document.getElementsByTagName("select")[0].selectedIndex].text + ", I've been clicked!")}
//SOME STYLING
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = "select { color: red; width:100%; min-width:100px;} button { width:100%; min-width:100px;} div {background-color: lightgray; width:10%; min-width:100px;} ";
document.body.appendChild(css);
/*
document.body.style.backgroundColor = "gray"
document.body.style.borderStyle = "solid"
document.body.style.borderColor = "yellow"
*/
document.getElementsByTagName("button")[0].id = "myButton"
document.getElementsByTagName("button")[0].className = "myButton"
//IE issues
/**
* trigger a DOM event via script
* @param {Object,String} element a DOM node/node id
* @param {String} event a given event to be fired - click,dblclick,mousedown,etc.
*/
var fireEvent = function(element, event) {
var evt;
var isString = function(it) {
return typeof it == "string" || it instanceof String;
}
element = (isString(element)) ? document.getElementById(element) : element;
if (document.createEventObject) {
// dispatch for IE
evt = document.createEventObject();
return element.fireEvent('on' + event, evt)
}
else {
// dispatch for firefox + others
evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
//document.getElementsByTagName("select")[0].onchange = function (evt){alert("I've changed!"); fireEvent("myButton", "click")}
fireEvent("myButton","click");
Simple DOM Manipulation, REPL Fun
clear body,
create dom element button, select box,
add data and events to each
- weelife.html (js1k/structure and transform)
- count (simple node/socketio)
- scrape js (node/cheerio/request)
- qwik-stand (meteor stand-up)
- Javascript Koans
- contacts (threejs, clojure/datomic API)
- eyejson (grid widget experiment)
- Buy Now Widget (custom tag, simple no framework single page app)
- Luhn Algorithm (I just like it)
- bodli coffeescript presentation
- reveal.js (this presentation node app)
- udacity.com/course/cs291 (free 3D course with three.js)
- scott.sauyet.com/javascript/...Compose (another functional talk)
- fn.js (under the covers [readable source], examples)
- WebPack - Flux - React - kefir - kraken
fn Links
//http://encosia.com/first-class-functions-as-an-alternative-to-javascripts-switch-statement/
function getItemPricing(customer, item) {
switch(customer.type) {
// VIPs are awesome. Give them 50% off.
case 'VIP':
return item.price * item.quantity * 0.50;
// Preferred customers are no VIPs, but they still get 25% off.
case 'Preferred':
return item.price * item.quantity * 0.75;
// No discount for other customers.
case 'Regular':
case default:
return item.price * item.quantity;
}
}
//step 1
// This should be namespaced somehow in the real world,
// not hanging off the global object.
pricing = {
// VIPs are awesome. Give them 50% off.
'VIP': 0.50,
// Preferred customers are no VIPs, but they still get 25% off.
'Preferred': 0.75,
// No discount for other customers.
'Regular': 1.0
}
function getItemPricing(customer, item) {
if (pricing[customer.type])
return item.price * item.quantity * pricing[customer.type];
else
return item.price * item.quantity * pricing.Regular;
}
//step 2
function getItemPricing(customer, item) {
var pricing = {
'VIP': function(item) {
return item.price * item.quantity * 0.50;
},
'Preferred': function(item) {
if (item.price <= 100.0)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
},
'Regular': function(item) {
return item.price * item.quantity;
}
};
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
//step 3
// Pricing.VIP.js
pricing = pricing || { };
pricing.VIP = function(item) {
return item.price * item.quantity * 0.50;
}
// Pricing.Preferred.js
pricing = pricing || { };
pricing.Preferred = function(item) {
if (item.price <= 100)
return item.price * item.quantity * 0.75;
// Else
return item.price * item.quantity;
}
// Pricing.Regular.js
pricing = pricing || { };
pricing.Regular = function(item) {
return item.price * item.quantity;
}
// Pricing.js
function getItemPricing(customer, item) {
if (pricing[customer.type])
return pricing[customer.type](item);
else
return pricing.Regular(item);
}
Switch => functional improvement
Original source
// Screen setup
c.width = 520; c.height = 520;
// Listeners
onkeydown = keydown;
addEventListener('mouseup', click);
// Loop
setInterval(simulate, 100);
// Variables
var size = 64;
var map = new Array(size*size);
var started = false;
// Initial cells
map[519] = map[520] = map[521] = map[457] = map[392] = 1;
// Draw map
function drawMap() {
a.clearRect(0,0,512,512);
for (i = 0; i < size; ++i) {
for (j = 0; j < size; ++j) {
if (map[i*size+j]) {
a.fillRect(j*8, i*8, 8, 8);
}
}
}
}
// Simulate world
function simulate() {
if (!started) return;
// Clone map
var clone = map.slice(0);
for(y = 0; y < size; ++y) {
for (x = 0; x < size; ++x) {
// Count neighbors
neighbors = 0;
for (i = -1; i <= 1; ++i) {
for (j = -1; j <= 1; ++j) {
if (!(i == 0 && j == 0) && y+i < size && y+i >= 0 && x+j < size && x+j >= 0) {
if (clone[(y+i)*size+x+j]) {
neighbors++;
}
}
}
}
// Modify cells
if (clone[y*size+x]) {
if (neighbors > 3 || neighbors < 2) {
map[y*size+x] = 0;
}
} else {
if (neighbors == 3) {
map[y*size+x] = 1;
}
}
}
}
drawMap();
}
// Add cell
function click(event) {
x = (event.clientX-8)/8|0;
y = (event.clientY-8)/8|0;
map[y*size+x] = 1;
drawMap();
}
// Change started
function keydown(event) {
started = !started;
}
// Draw initial map
drawMap();
wee game of life (see js1k)
//add Arrays
var x = [1]
var y = [1,2,3]
f = function(x,y){
var shorter = x.length < y.length ? x : y;
var longer = x.length < y.length ? y : x;
var sl = shorter.length;
var ll = longer.length;
var z = [];
for (var i=0;i < sl; i++) {
z[i] = x[i] + y[i];
};
for (var j=sl;j < ll;j++){
z[j] = longer[j];
};
return z
}
var a = [4,5,6,7];
//f(f(x,y),a) [recursive and/or reducable due to commutative nature of addition (or multiplication)]
[x,y,a].reduce(function(sum,unit,index,arr){return f(sum,unit)},[]);
Add Array Values
// Variant of Avraham Plotnitzky's String.prototype method mixed with the "fast" version
// see: https://sites.google.com/site/abapexamples/javascript/luhn-validation
ccValidator = function(){
return function luhnChk(self) {
var luhn = self.value,
len = luhn.length,
mul = 0,
prodArr = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]],
sum = 0;
while (len--) {
sum += prodArr[mul][parseInt(luhn.charAt(len), 10)];
mul ^= 1; //shorthand for mul = mul ^ 1, bitwise XOR with 1 so it flips the 0 bit, ie 0 => 1 or 1=> 0 on 0 bit 6 => 1 [110 => 111]
}
return sum % 10 === 0 && sum > 0;
};
}
Luhn Example
function assert(value, desc) {
if (!true) {
var li = document.createElement('li');
li.className = value ? 'pass' : 'fail';
li.appendChild(document.createTextNode(desc));
document.getElementById('results').appendChild(li);
}
else{
return "";
}
}
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = ".pass:before {content: 'PASS: '; color: blue; font-weight: bold} .fail:before {content: 'FAIL: '; color: red; font-weight: bold}";
document.body.appendChild(css);
tiny tester
itsme video widget
//Begin Server (node app.js) [npm install express, npm install socket.io]
var app = require('express')()
, server = require('http').createServer(app)
, io = require('socket.io').listen(server)
, counter = 0;
var port = 4001;
server.listen(port);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
io.set('log level', 1);
io.sockets.on('connection', function (socket) {
console.log(counter);
socket.emit('count', counter);
socket.on('inc', function () {
counter++;
console.log('inc', counter);
io.sockets.emit('count', counter);
});
});
//End Server
//Begin Client (index.html)
<title>real time fun</title>
<div id="count"></div>
<button>inc</button>
//End Client
Node -- super simple socket.io example
Reveal Links
THE END
BY Jon Tiemann, jtiemann@digitalpersonae.com
Transformation & Deconstruction
By Jon Tiemann
Transformation & Deconstruction
Transformation & Deconstruction
- 317