Transformations

fetch, shape, transform, render, wait

Created by Jon Tiemann / @jtiemann

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 &gt; 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 =&gt; 1 or 1=&gt; 0 on 0 bit 6 =&gt; 1 [110 =&gt; 111]
      }
      return sum % 10 === 0 &amp;&amp; sum &gt; 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 &gt;= $(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 &lt; $("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
//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 &lt;= 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 &lt;= 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 &lt; size; ++i) {
        for (j = 0; j &lt; 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 &lt; size; ++y) {
        for (x = 0; x &lt; size; ++x) {
        
            // Count neighbors
            neighbors = 0;
            for (i = -1; i &lt;= 1; ++i) {
                for (j = -1; j &lt;= 1; ++j) {
                
                    if (!(i == 0 &amp;&amp; j == 0) &amp;&amp; y+i &lt; size &amp;&amp; y+i &gt;= 0 &amp;&amp; x+j &lt; size &amp;&amp; x+j &gt;= 0) {
                        if (clone[(y+i)*size+x+j]) {
                            neighbors++;
                        }
                    }
                }
            }
            
            // Modify cells
            if (clone[y*size+x]) {
                if (neighbors &gt; 3 || neighbors &lt; 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 &lt; y.length ? x : y;
var  longer = x.length &lt; y.length ? y : x;
var  sl = shorter.length;
var  ll = longer.length;
var z = [];

for (var i=0;i &lt; sl; i++) {
  z[i] = x[i] + y[i];
};

for (var j=sl;j &lt; 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 =&gt; 1 or 1=&gt; 0 on 0 bit 6 =&gt; 1 [110 =&gt; 111]
      }
      return sum % 10 === 0 &amp;&amp; sum &gt; 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

THE END

BY Jon Tiemann, jtiemann@digitalpersonae.com

Transformation & Deconstruction

By Jon Tiemann

Transformation & Deconstruction

Transformation & Deconstruction

  • 317