Here's a little dive into some exciting new features.
Inspiration and examples pulled from ES6fiddle and Egghead.io.
var is still a perfectly valid declaration for new variables, but the let
keyword introduces some more predictable behavior than that which comes packaged with vanilla javascript.
The keyword let
is not hoisted like var
, so a declaration made with let
is only created at the time of its declaration.
In respect to block scope, this means that you do not need to be in the scope of a function in order to have different values for the same variable names:
var i = "outside";
{
var i = "inside";
}
console.log(i); // "inside"
var i = "outside";
{
let i = "inside";
}
console.log(i); // "outside"
A for loop
using var
, like we're used to, instantiates a variable declared in the front, and then changes the value on every iteration. This can trip us up pretty easily when we pass our first-class functions by reference, and bind values inside of those functions to the variable. For instance:
var functions = [];
// var keyword declares 'i' once
for (var i = 1; i <= 5; i++){
functions.push(function(){
console.log(i);
});
}
functions.forEach(function(f){
f();
});
yields:
6
6
6
6
6
We know to expect this behavior from javascript, but that doesn't mean we have to like it. Because i
is declared once, any function that references it should expect its value to equal whatever the value is at the time of the function's invocation. Because we invoke all the functions here after we're done incrementing the same instance of i
, each of these functions will reference its final value.
Using the let keyward inside of our for loop
can provide us the behavior that we were most likely hoping for:
var functions = [];
// let keyword declares a new 'i' in block scope, each time
for (let i = 1; i <= 5; i++){
functions.push(function(){
console.log(i);
});
}
functions.forEach(function(f){
f();
});
yields:
1
2
3
4
5
Nice.
When working with objects with which you are only concerned with particular keys and values, ES6 provides a convenient way of quickly assigning references to those inner keys.
let [one, two] = [1, 2];
let {three, four} = {three: 3, four: 4};
console.log(one, two, three, four);
yields:
1 2 3 4
I can't be the only guy that, in an itertive loop on more than one unfortunate occasion, referred to the index of an array when my intention was to use the value stored at that index. Well, gone are those days. Behold the of
keyword:
var arr = ['A','D','A','M'];
var result = '';
for (var i of arr){
result += i;
};
console.log(result);
yields:
"ADAM"
The spread
operator enables us to manipulate values of a collection, as a collection, without having to specifically target them. It works a lot like an each
or map
method that we would ordinarily pull in via underscore
or lodash
.
var front = [1,2,3,4,5];
var back = [6,7,8,9,10];
front.push(back)
console.log(front)
yields:
[1,2,3,4,5,[6,7,8,9,10]]
Of course, but... ick. spread
syntax to the rescue!
var front = [1,2,3,4,5];
var back = [6,7,8,9,10];
front.push(...back)
console.log(front)
yields:
[1,2,3,4,5,6,7,8,9,10]
Countless times, I have longed for a more semantic way to declare default values for the expected arguments of a function.
var thisThing = thisThing || somethingElse;
We all know what it means, but that doesn't mean it should be necessary. be able to specify default values in the function declaration is convenient and a familiar feature from myriad other languages. Finally, it's here.
function defaults(x = 3, y = 6){
return [x,y];
}
so,
console.log(defaults());
yields:
[3,6]
and
console.log(defaults(1,1));
yields:
[1,1]
Lovely.
A generator is like a function that can be paused and resumed. It can yield
a different value each time (like a return
, but without finishing), if that's what you'd like. This example from ES6fiddle shows clearly how we can create a range
function.
function* range(start, end, step) {
while (start < end) {
yield start;
start += step;
}
}
for (let i of range(0, 10, 2)) {
console.log(i);
}
yields:
0
2
4
6
8
Generators come with a promise-like API, including a .next()
method. Calling that method will make your generator step to the next yield
declaration. In this example from Egghead.io, you can even go so far as to create an infinite loop in a generator:
function* noStackOverflow(){
let x = 0;
let y = 0;
while(true){
yield {x:x, y:y}
x += 2;
y += 1;
}
}
var generator = noStackOverflow();
console.log(generator.next().value);
console.log('Do something else');
console.log(generator.next().value);
yields:
{x:0,y:0}
Do something else
{x:2,y:1}
This generator will continue to generate a new object and set of values for us upon every request.
There are many other great features to get excited about that are not covered here. To name a few:
Promise
constructorclass
declaration=>
notation, which carries much of the same functionality with which it comes in Coffeescript.Javascript, NodeJS, Express, AngularJS, Ionic, jQuery, HTML5, CSS, Git
Ruby, MongoDB, Redis, MySQL, BackboneJS, D3, Mongoose, ActiveRecord, Sinatra, Rails, CoffeeScript, NPM, bower, EJS, Jade, Haml, Cordova, Passport, XSS & CSRF, AWS S3, AWS EC-2, Digital Ocean, Heroku, *NIX
Famo.us, Docker, CouchDB, Python, Django, PHP, Java, C
San Francisco, CA | 2014
San Francisco, CA | 2014
San Francisco, CA | 2014
Brooklyn, NY | 2013
NY, NY | 2001 - 2013
San Francisco, CA | Jan - Jul 2014
Stanford via Coursera | Aug 2013
I'm a musician for life, an avid builder of intangible things, a proud generalist, and yet, highly detail-oriented.
]]>NodeJS comes nicely equipped with a module, net, for commuinicating via the TCP layer. Simply require('net')
and you are now equipped to open a TCP socket. The module comes with a class Socket, and new socket connections can be instantiated when needed:
var tcpSock = require('net');
var client = new tcpSock.Socket;
var socket = client.connect(PORT, HOST);
NodeJS also features a Buffer class, that will assemble raw binary data packets into an array-like object that can contain only integers. One instance of the Buffer class represents a specific, fixed size memory allocation outside of the V8 heap. I use it to collect incoming data packets, as well as to properly assemble my outgoing binary data.
var buffer = new Buffer(0, 'hex');
// listen for incoming data
socket.on("data", function(data){
// a custom function for logging more readable binary
logDataStream(data)
// pack incoming data into the buffer
buffer = Buffer.concat([buffer, new Buffer(data, 'hex')]);
})
The incoming binary was organized as C structs, and I am able to parse the data fairly easily using a great npm library called node-struct. The node-struct library allows the developer to define the structs in terms of javascript objects with strictly typed, pre-allocated fields. There are types for 8bit words through 32 bit, signed and unsigned, big and little endian. There are also fields for ascii and other encoded characters, as well as fields for other structs within structs. For example:
var Struct = require('struct').Struct;
function makeAndParsePersonFromBinary(buffer){
var person = new Struct()
.('word8', 'Sex') // 0 or 1 for instance
.('word32Ule', 'Age')
.('chars','Name', 64);
person._setBuff(buffer);
return person;
};
var incomingPerson = makeAndParsePersonFromBinary(buffer);
Once the struct has been seeded with the binary buffer, you can now access the values from the binary packets, properly parsed as fields on the struct:
var personName = incomingPerson.get('Name');
As you can probably see, this node-struct library is a very useful tool when working in NodeJS with binary streams that represent structs.
The data is parsed and I'm ready to start working with it in my app.
Sometimes I just want to see the raw ints come in and out. FWIW, here's how I'm logging my incoming and outgoing binary streams:
function logDataStream(data){
// log the binary data stream in rows of 8 bits
var print = "";
for (var i = 0; i < data.length; i++) {
print += " " + data[i].toString(16);
// apply proper format for bits with value < 16, observed as int tuples
if (data[i] < 16) { print += "0"; }
// insert a line break after every 8th bit
if ((i + 1) % 8 === 0) {
print += '\n';
};
}
// log the stream
console.log(print);
}
]]>FYI: Most of what I know about testing with protractor, I learned here in the ng-newsletter. You will find very detailed instructions on setting it up, and learn a little about the technology protractor is built upon (Selenium's Webdriver).
Using grunt to build my project, and after installing via npm install grunt-protractor-runner
, running my protractor e2e tests is as simple as running my karma unit tests. One command, grunt test
, and tests do the rest of the hard work.
Protractor and Webdriver give you access to a browser
object, through which you define the user's experience and behavior inside of your test.
I experienced a very confounding problem when writing my e2e tests, and it brought an interesting protractor feature to light. Because my module is dependent on various uses of angular's $timeout, I seemed unable to coordinate protractor's behavior without experiencing it timing out, or blowing through the tests at the improper times. I found the issues documented here and this lead to the eventual solution to my problem:
browser.ignoreSynchronization = true;
When using ignoreSynchonization
before running my tests, I'm able to instruct the browser to observe sleep timers at various times, and can verify that the correct items are on screen, or hidden, at the appropriate times.
Because there are several phases during the use of ngLazy in which we can expect different things from our view, I needed a way to allow for Protractor to run different tests during each phase of the cycle. This was fairly easy once I made use of the method:
browser.sleep()
Probably the primary feature of ngLazy is reliant on the user scrolling to the bottom of the list, in order to trigger ngLazy to begin appending more items to the dom. This behavior was also pretty trivial to recreate in protractor! The executeScript
method provides an api that takes DOM API arguments and executes them, while returning a promise. When the promise resolves, proceed with more instructions:
browser.executeScript('window.scrollTo(0,' + 800 + ');').then(function(){
browser.sleep(2000);
// range is defined previously
expect(element.all(by.css('.ng-binding')).count()).toEqual(range * 2);
})
I'll include the current family of tests below, to demonstrate the kind of integrated tests I'm running. The assertions include:
* it should immediately display a spinner
* it should have a spinner-color that matches the configuration
* it should have as many ng-repeated items as the scope indicates
* it should have an element in the DOM that represents the bottom of the list
* it should add elements to the DOM when it scrolls to the bottom of the list
describe("ngLazy-demo", function(){
browser.driver.manage().window().maximize();
browser.get('/#');
browser.ignoreSynchronization = true;
describe("index", function () {
it("should display the correct title", function(){
expect(browser.getTitle()).toBe('ngLazy-Demo');
});
it("should have a lazy-load element", function(){
expect(element(by.css('lazy-load')).isPresent()).toBe(true);
});
});
var range;
describe("lazy-load directive", function(){
it ("should immediately display a spinner", function(){
expect(element(by.css('.ng-hide')).isPresent()).toBe(false);
});
it("should have a spinner-color that matches the configuration", function(){
var color;
var loadingWidget = browser.findElement(by.css('.loading-widget'));
loadingWidget.getAttribute('style').then(function(color){
// ugly way of getting the color string from the directive
color = (((color
.split('border-color:')[1])
.split(';')[0])
.split('transparent ')[1]).trim()
.split(' rgb')[0];
var spinnerElement = browser.findElement(by.model('spinnerColor'));
spinnerElement.getAttribute('value').then(function(val){
expect(color).toEqual(val);
})
})
})
it("should have as many ng-repeated items as the scope indicates", function(){
var rangeElement = browser.findElement(by.model('range'));
rangeElement.getAttribute('value').then(function(val){
browser.sleep(4000);
range = parseInt(val);
var repeats = element.all(by.css('.ng-binding')).count();
expect(repeats).toEqual(range);
});
});
it("should have an element in the DOM that represents the bottom of the list", function(){
expect(element(by.css('#lazy-bottom')).isPresent()).toBe(true);
});
it("should add elements to the DOM when it scrolls to the bottom of the list", function(){
browser.sleep(2000);
browser.executeScript('window.scrollTo(0,' + 800 + ');').then(function(){
browser.sleep(2000);
expect(element.all(by.css('.ng-binding')).count()).toEqual(range * 2);
})
})
});
})
I am looking to fill out this suite with even more useful tests, so I welcome any suggestions! Please feel free to fork and pull request from the demo app's repo.
]]>My concerns had little to do with the library's ability to handle edge cases, and much more to do with a strong desire to open up my code to the open source community. I didn't feel it was responsible to do this -- to solicit other people for their time and thoughtfulness -- without providing some automated indicator that the library has not been broken by a change that is made. So, I got down to work and wrote some tests.
The recommended engine for unit testing an AngularJS app is karma, and by default, it uses the Jasmine testing framework. npm install karma
, followed by karma init
got me moving pretty quickly, but there were configuration kinks that took a minute to iron out.
A big question was raised before I could begin, though. How was I going to adequately demonstrate and test this library, of which there was but only a few features packed into a module, without a standalone purpose? My solution was to create a demonstration app for the features of the library, and in doing so, integrate my test suite with the demo. The result - http://ng-Lazy.com. My expectations are that this app will be integrated in the development cycle for further iterations on the library, and that the tests will be run against it.
Unit tests should be as decoupled from all pieces of an application as possible, in order to only test the operation of the single feature being tested. So this begged the question, what are the elements of this feature whose functionality needs to be varified in order to ensure nothing has been broken? Understanding which concepts to test was as challenging as figuring out how to test them. This would probably have been easier if the library had been developed in a TDD style!
Regardless, I combed through the pieces of ngLazy that make it tick and came away with some key assertions:
the lazyLoader Factory should have the proper methods
it should have a div with id 'bottom'
when repeated items are rendered, the bottom div should follow the last item
it should have a scope with keys for every lazy-load attribute
Now that I had some guidelines to shoot for, I needed to mock up their execution.
Some vital parts of mocking the Angular environment in which the piece you are testing operates:
beforeEach(module('ngLazy'));
beforeEach(inject(['$rootScope','$controller','$injector',function($rootScope, $controller, $injector){
lazyLoader = $injector.get('lazyLoader');
$scope = $rootScope.$new();
}]));
it('should have a lazyLoader Factory', function(){
expect(lazyLoader).not.toBe(undefined);
});
it('the lazyLoader Factory should have the proper methods', function(){
expect(lazyLoader.configure).not.toBe(undefined);
expect(lazyLoader.getData).not.toBe(undefined);
expect(lazyLoader.load).not.toBe(undefined);
});
In order to test the directive that this module provides, we need to mock an angular element that invokes it, and give it some controller data to validate the features are functioning.
NOTE: if your directive has isolate scope whose properties you will be testing, use the isolateScope() method on your angular element, AFTER you have invoked $compile
on it with a scope.
describe('Directive', function(){
beforeEach(module('ngLazy'));
var element, $scope, list, bottom, elementScope;
beforeEach(inject(['$rootScope','$compile', function($rootScope, $compile){
$scope = $rootScope.$new();
$scope.data = {};
$scope.data.list = [
"item1",
"item2",
"item3",
"item4",
"item5"
];
element = angular.element(
'<lazy-load' +
' lazy-data="data"' +
' lazy-data-service="dataService"' +
' lazy-fetch-method="getList"' +
' lazy-range=" {{ range }}"' +
' lazy-data-collection-key="list"' +
' lazy-data-keys="[\'list\']"' +
' lazy-start-delay="{{ startDelay }}"' +
' lazy-append-delay="{{ appendDelay }}"' +
' lazy-spinner-color="{{ spinnerColor }}">' +
'<div ng-repeat="item in data.list">' +
'<h4>{{ item }}</h4>' +
'</div>' +
'</lazy-load>');
$scope.$apply();
$compile(element)($scope);
elementScope = element.isolateScope();
}]));
it('should not break ng-repeat', function(){
$scope.$digest();
list = element.find('h4');
expect(list.length).toBe(5);
})
it('should have a div with id=\'bottom\'', function(){
bottom = element.find('div')[3];
expect(bottom.id).toBe('lazy-bottom');
})
it('when repeated items are rendered, the bottom div should follow the last item', function(){
$scope.$digest();
bottom = element.find('div')[8];
expect(bottom.id).toBe('lazy-bottom');
})
it('should have a scope with keys for every lazy-load attribute', function(){
expect(elementScope.lazyData).not.toBe(undefined);
expect(elementScope.lazyDataCollectionKey).not.toBe(undefined);
expect(elementScope.lazyDataKeys).not.toBe(undefined);
expect(elementScope.lazyFetchMethod).not.toBe(undefined);
expect(elementScope.lazyRange).not.toBe(undefined);
expect(elementScope.lazySpinnerColor).not.toBe(undefined);
expect(elementScope.lazyAppendDelay).not.toBe(undefined);
expect(elementScope.lazyStartDelay).not.toBe(undefined);
expect(elementScope.lazyDataService).toBe('dataService');
});
})
})
I swear, I wrote these tests and now I sleep much better a night. Next up, I'll try my hand at some notes about end-to-end testing ngLazy.
]]>First thing's first:
npm install karma
followed by:
karma init
The karma tests are configured via a file that is named karma.conf.js
by convention. In it, we find a number of config properties on an object passed to the config.set method (config is the parameter that is passed to karma at the time of invocation). A little clarification about these properties would have helped quite a bit, namely:
{
files: [...],
}
This files array must contain paths for all scripts that the app you are testing is dependent on. Not just your spec files. That means angular and all your other vital dependencies, including in this case, the path to ngLazy.
There is also an exclude:[...]
property, so that you can include files above using a wildcard *
and then exclude specific files within those directories.
Karma launches its own webserver, but this must run CONCURRENT to the locally served app you are testing. By default, it uses port 9876. But this is an important point to understand. You must also serve your app BEFORE you run tests and launch the karma server.
Running your tests will be simplified with a build tool like grunt, and the grunt-karma npm package. This will run the necessary karma commands after defining a karma step in your grunt test script. If you don't use grunt, that is cool too! But don't forget, before you run your tests, you must start the karma server:
karma start path/to/tests/karma.conf.js
Then, to run the tests:
karma run path/to/tests/karma.conf.js
]]>A little while ago, I wrote a blog post about implementing a lazy load / infinite scroll in an app I was hacking on. The logic was spread between a controller and directive, and it needed decoupling. It couldn't be re-used without copying a ton of code into any controller that wanted to use the feature. Further, implementing my spinner required logic throughout the controller that exists just to manipulate the DOM. It was a fine first implementation, but now it's time to refactor. I planned to:
The end goal is for the user to be able to implement a lazy-loading infinite scroll and spinner via a simple directive.
The result is ngLazy.
Now my controller is as agnostic to this feature as any other controller:
.controller('listController', ['$scope', function($scope){
$scope.data = {
mongo : [],
gh : {},
keen : {}
};
}])
The feature still requires a good amount of configuration per use case, so I have designed it with a factory and directive which takes configuration via the directive's element's attributes:
<lazy-load
lazy-data="data"
lazy-data-service="dataService"
lazy-fetch-method="getData"
lazy-range="12"
lazy-data-collection-key="mongo"
lazy-data-keys="['mongo','gh','keen']"
lazy-start-delay="150"
lazy-append-delay="1000"
lazy-spinner-color="#4FA7D9"
>
<table>
<thead>
<tr>
<td>NAME</td>
<td>STATS</td>
<td>COMMENTS</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="datum in data.mongo">
<td class="cohort-pic">
<span class="cohort-name">{{ datum.First }} {{ datum.Last }}</span><br>
</td>
<td class="cohort-stats-col cohort-score">
<div> some stats</div>
</td>
<td ><textarea class="form-control cohort-comments-form" rows="3">{{ datum.comments }}</textarea>
</td>
</tr>
</tbody>
</table>
</lazy-load>
(function(angular){
'use strict';
angular.module('ngLazy',[
'ngLazy.factories',
'ngLazy.directives'
]);
angular.module('ngLazy.directives',[])
.directive('lazyLoad', ['$injector','$window','$document','$timeout','$rootScope',function($injector, $window, $document, $timeout, $rootScope){
var appendAnimations = function(){
var style = document.createElement('style');
var keyframes = '@-webkit-keyframes spin {\n' +
'\t0%{-webkit-transform: rotate(0deg);}\n' +
'\t100%{-webkit-transform: rotate(360deg);}\n' +
'}\n' +
'@keyframes spin{\n' +
'\t0%{transform: rotate(0deg);}\n' +
'\t100%{transform: rotate(360deg);}\n' +
'}';
style.innerHTML = keyframes;
document.head.appendChild(style);
};
var makeSpinner = function(el){
el.css({
WebkitBoxSizing: 'border-box',
boxSizing: 'border-box',
display: 'block',
width: '43px',
height: '43px',
margin: 'auto',
borderWidth: '8px',
borderStyle: 'solid',
borderColor: 'transparent rgb(85, 148, 250) rgb(85, 148, 250) rgb(85, 148, 250)',
borderRadius: '22px',
animation: 'spin 0.8s linear infinite',
WebkitAnimation: 'spin 0.8s linear infinite'
});
return el;
};
return {
restrict: 'E',
scope: {
lazyData : '=',
lazyDataCollectionKey : '@',
lazyDataService : '@',
lazyFetchMethod : '@',
lazyRange : '@',
lazyDataKeys : '=',
lazyStartDelay : '@',
lazyAppendDelay : '@',
lazySpinnerColor : '@'
},
transclude: true,
template: '<div ng-transclude></div>' +
'<div class=\'col-md-12 loading\' ng-hide=\'spinner.hide\'>' +
'<div class=\'loading-widget\'></div>' +
'</div>'+
'<div id=\'lazy-bottom\'></div>',
link: function(scope) {
var winEl = angular.element($window),
win = winEl[0],
lazyBottom = angular.element(document.querySelector('#lazy-bottom'))[0],
lazyBottomOffset = lazyBottom.offsetTop - 20,
lazyLoader = $injector.get('lazyLoader'),
dataService = $injector.get(scope.lazyDataService),
loadingWidget = angular.element(document.querySelector('.loading-widget')),
hasRun = false,
loading = false;
appendAnimations();
loadingWidget = makeSpinner(loadingWidget);
scope.spinner = { hide : false };
var lazyLoad = function(){
lazyLoader.configure({
data : scope.lazyData,
collectionKey : scope.lazyDataCollectionKey,
fetchData : dataService[scope.lazyFetchMethod],
range : scope.lazyRange,
dataKeys : scope.lazyDataKeys,
startDelay : scope.lazyStartDelay,
appendDelay : scope.lazyAppendDelay
});
lazyLoader.load().then(function(data){
if(!hasRun){
angular.forEach(Object.keys(data), function(key){
scope.lazyData[key] = data[key];
});
} else {
scope.lazyData[scope.lazyDataCollectionKey] = data[scope.lazyDataCollectionKey];
}
loading = false;
});
};
$rootScope.$on('hideLoading', function(){ scope.spinner.hide = true; });
$rootScope.$on('showLoading', function(){ scope.spinner.hide = false; });
winEl.bind('scroll', function(){
if (!loading && win.scrollY >= lazyBottomOffset) {
loading = true;
lazyBottomOffset = lazyBottomOffset * 2;
win.requestAnimationFrame(function(){
scope.$apply(function(){
lazyLoad();
lazyBottomOffset = lazyBottom.offsetTop-10;
});
});
}
});
lazyLoad();
}
};
}]);
angular.module('ngLazy.factories',[])
.factory('lazyLoader', ['$timeout','$rootScope', '$q', function($timeout, $rootScope, $q){
var cache = { data : {} },
config,
data,
collectionKey,
fetch,
responseKeys,
range,
appendDelay,
startDelay;
return ({
configure: function(options){
config = options;
},
getData : function(){
data = config.data;
collectionKey = config.collectionKey;
fetch = config.fetchData;
responseKeys = config.dataKeys;
range = config.range;
appendDelay = config.appendDelay;
startDelay = config.startDelay;
var deferred = $q.defer();
$rootScope.$broadcast('showLoading');
if (!cache.data[collectionKey]) {
fetch().then(function(res){
angular.forEach(responseKeys, function(key){
cache.data[key] = res.data[key];
if (key === collectionKey) {
data[key] = [];
data[key] = data[key].concat(cache.data[key].splice(0, range));
} else {
data[key] = cache.data[key];
}
});
deferred.resolve(data);
$rootScope.$broadcast('hideLoading');
});
} else {
$timeout(function(){
data[collectionKey] = data[collectionKey].concat(cache.data[collectionKey].splice(0, range));
deferred.resolve(data);
$rootScope.$broadcast('hideLoading');
}, appendDelay);
}
return deferred.promise;
},
load : function(){
var deferred = $q.defer();
var _this = this;
$rootScope.$broadcast('showLoading');
var loadTimer = $timeout(function(){
_this.getData().then(function(col){
deferred.resolve(col);
});
}, startDelay);
loadTimer.then(function(){
$timeout.cancel(loadTimer);
});
return deferred.promise;
}
});
}]);
})(angular);
In the future, I am looking to make this module more configurable, such as adding a custom spinner or easily changing the spinner styling. I also intend to further refactor this to remove elements from the DOM when they have left the screen, and re-add them upon scrolling up.
The configuration is explained in the repo's README, but the main-takeaway is this: with the <lazy-load>
tag, simply wrap the element that will be displaying your list of info. Tell the lazy-load library what service you will use to fetch that data and how you would like it to be divided and presented. ngLazy handles the rest.
NEXT UP: Testing ngLazy
]]>This implemenation is dependent on an API that responds once with the entire data set that will need to be fetched. Originally, I designed the api to respond to multiple calls from the client because I thought that returning too much would negetivley impact browser performance. But, that was naive. The browser can handle this without breaking a sweat.
///////////////////////
// listController.js //
///////////////////////
angular.module('myApp').controller('listController', ['$scope','dataService','$timeout', function($scope, dataService, $timeout){
$scope.data = {};
$scope.spinner = {};
$scope.data.mongo = [];
// used to show and hide the table element that will display the data
$scope.isLoading = true;
// used to manipulate the element containing the spinner once the data table
// is displayed
$scope.spinner.hide = false;
// this will store the bulk of the data from the initial response.
// then we will splice items off as needed
var cache = { data: { mongo: [] } };
var getData = function(){
// check that the view-bound data set is empty
if ($scope.data.mongo.length < 1){
dataService.getData()
.then(function(res){
// display the table element
$scope.isLoading = false;
// declare the $timeout with a reference in order to cancel
// it later. the $timeout invokes the AngularJS digest cycle
// and will update the view bound to the data
var appendDataTimer = $timeout(function(){
cache.data.mongo = res.data.mongo;
// splice some data to append to the view
$scope.data.mongo = $scope.data.mongo.concat(cache.data.mongo.splice(0, 12));
// assign some other data hashes from the response to the $scope
// so they can be referenced in the view
$scope.data.gh = res.data.gh;
$scope.data.keen = res.data.keen;
// remove the spinner from the view
$scope.spinner.hide = true;
},150);
// invoke the timeout and remove it when the promise resolves
appendDataTimer.then(function(){
$timeout.cancel(appendDataTimer);
})
});
} else {
// the view contained data, so continue to splice from the cache and add // to the view data
$scope.data.mongo = $scope.data.mongo.concat(cache.data.mongo.splice(0, 12));
// delays feel more 'magical'
$timeout(function(){
$scope.spinner.hide = true;
},1000);
}
};
// to be invoked on the scroll event in our directive, which will be
// triggered once we scroll through some data that is already displayed
$scope.loadMore = function(){
$scope.spinner.hide = false;
var loadTimer = $timeout(function(){
getData();
}, 2000)
loadTimer.then(function(){
$timeout.cancel(loadTimer);
});
};
// invoke the retrieval of data
getData();
}])
/////////////////////////////////////////
// _list_partial.html (without styles) //
/////////////////////////////////////////
<table class="table table-striped table-hover" ng-if="!isLoading" when-scrolled="loadMore()">
<thead>
<tr>
<td>NAME</td>
<td>STATS</td>
<td>COMMENTS</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="datum in data.mongo">
<td>
<span>{{ datum.First }} {{ datum.Last }}</span><br>
</td>
<td class="cohort-stats-col cohort-score"> some stats </td>
<td ><textarea rows="3">{{ datum.comments }}</textarea></td>
</tr>
</tbody>
</table>
<div ng-hide="spinner.hide">
<img src='http://adamrichman.com/img/spinner.gif' >
</div>
//////////////////////////
// lazyLoadDirective.js //
//////////////////////////
angular.module('myApp')
.directive('whenScrolled', function($window, $timeout) {
return {
restrict: "A",
link: function(scope, element, attr) {
var top = angular.element($window)[0].screenTop;
var origHeight = angular.element($window)[0].screen.height;
var height = (origHeight * 0.9);
// bind the digest cycle to be triggered by the scroll event
// when it exceeds a threshold
angular.element($window).bind('scroll', function() {
if (angular.element($window)[0].scrollY >= (height)) {
// show the spinner when triggered
scope.spinner.hide = !scope.spinner.hide;
angular.element($window)[0].requestAnimationFrame(function(){
// invoke the function passed into the 'whenScrolled' attribute
scope.$apply(attr.whenScrolled);
// increment the threshold
height += (origHeight * 1.5);
})
}
});
}
}
})
]]>Stylr is a mobile app that, for lack of a better explanation, is probably best referred to as "Tinder for fashion". My friend Brendon Verissimo and I conceived and developed it. The idea behind the app is that users can tell the app about their tastes, and the app will track their preferences, eventually revealing their most prevelent style taste, and then go on to recommend actual fashion items to the user.
The stack for Stylr consists of:
mongoDB instance, hosted by mongoLab
NodeJS / Express server, hosted on a Digital Ocean droplet
AngularJS / Ionic Framework web app
Apache Cordova to wrap the app in a mobile native container
The performance of the web app on a phone, in relationship to native apps, is pretty remarkable. The ionic framework is basically bootstrap for your mobile web app. While we didn't use many of its features, it specializes in recreating familar native views, flows and gestures, all in an AngularJS app that's running in a webkit browser.
One of the key components of Ionic's framework is the Angular-UI Router. Once I got to know this module, I basically vowed to never again use Angular's native $routeProvider. The Angular-UI Router allows for nested states, which makes it very easy to implement views as parents and children, allowing for a pattern that much closer suits the way we think about views. It gives the developer a stateProvider, through which states can be accessed through various means.
Perhaps the most difficult challenge during the developemnt of Stylr was implementing the horizontal swipe-cards. I started with an old example of vertically swiped cards that behaved quite differently. A few days of tweaking and I was able to dictate the behaviors I was looking for - namely, the proper interpretation of the user's intent, and the corresponding actions. For instance:
if the user is moving at an accelerating velocity at the moment of release, they likely intent to toss this card. So this must trigger an animation that continues the current trajectory of the card view. It must also trigger the logic to pop the last card off the stack of cards to be played.
if the user is not accelerating at the moment of releasing their finger, it's likely that they intend to reconsider their own intent. The appropriate action here was to snap the card back to it's starting place, and be sure not to register any preference from the user.
The original card interaction demo has since been iterated on (check the pull requests), and can be found here.
]]>describe('Video Routes API', function () {
var server;
before(function (done) {
process.env['MONGOHQ_URL']="mongodb://ourApp:ourPWD@subdomain.mongohq.com:10042/ourTestDB";
server = child.exec('node web.js &', { killSignal: "SIGTERM"}, function(err, stdout, stderr){
console.log('something', err, stdout,stderr);
done();
});
});
after(function(done){
server.kill();
done();
});
}
What we were hoping to achieve here was the ability to spin up our server instance from inside of the test suite. This way we could set our environmental variable for the database to direct connections to our remote test database. Also, this would provide for a less granular set of integration tests.
We used the child.exec() method because we expected it to asynchronously start our server from the shell, and return a callback when it was ready.
First time: All tests pass
Second time:
0 passing (8s)
6 failing
1) User API Video Routes API GET /clips should return 200:
Error: timeout of 2000ms exceeded
at null.<anonymous> (/usr/local/lib/node_modules/mocha/lib/runnable.js:175:14)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
...
6) User API Video Routes API should return 200 upon reciept of favorite of a video:
Error: connect ECONNREFUSED
at errnoException (net.js:901:11)
at Object.afterConnect [as oncomplete] (net.js:892:19)
We were wrong. A few things were fairly obvious:
the callback was never being called!
our tests were unable to connect to our server.
Fine, but why only every other time? We hypothetsized that our server was not hanging up after our tests were done running, but it didn't make a whole lot of sense that it would be unavailable for reconnections.
After a long, painful exploration of the native child process functions in nodejs (really, I am still quite grateful for the opportunity to have become familiarized), we understood that our callback wasn't being called because there was never any "finish" to the buffered output of a still-running server. child.exec() was not the method we needed. Sure enough, we verified that our server process was still running after the tests ran, and on the next subsequent attempt, our tests connected and passed. Apparently our server.kill() method wasn't getting the job done for us. As it turns out, child.exec() isn't going to be our friend for that method either.
The answer was two-fold:
substitute child.exec() with the synchronous child.spawn().
implement a timeout before the tests run, just to give the server some time to get up and running before we try to test it.
It's kind of a hacky solution, we know. In a world where we had more time with this project, we would have explored having our server emit a 'ready' message for our tests to listen to. For now, though, this worked just fine:
describe('Video Routes API', function () {
var server;
before(function (done) {
process.env['MONGOHQ_URL']="mongodb://ourApp:ourPWD@subdomain.mongohq.com:10042/ourTestDB";
server = child.spawn('/usr/local/bin/node', ['/Users/path/to/ourApp/web.js'], {detached: true, stdio: [ 0, out, err ]});
setTimeout(done, 1000);
});
after(function(done){
server.kill();
done();
});
}
Now our tests run properly every single time, and they pass to-boot!
Consider child.spawn() when you need to start a different program from inside of your running, node program. And don't presume that your server is ready for connections immediately after instantiating it!
]]>We were trying to facilitate direct uploads from our client to our S3 bucket, but without exposing any credentials that would need to be present on the client side. The answer to doing this requires that you deliver the client a temporary, signed URL or policy that is generated on your own server and returned to the client in order to accompany the payload to S3. Unfortunately this is where the SDK stopped working for us.
I scoured the web for days in search of solutions and found myriad similar stack overflow questions and blog entries on this very challenge. Still, I came up empty handed when I attempted to integrate many of the code examples, I presume because Amazon has since changed their API, or because of some other finicky detail that I wasn't identifying. I suppose that this kind of request just screams CORS and I understand why. I finally found my way to code that worked for us after finding a few posts that shined some meaningful light on the solving this problem. They are here, here and here.
My takeaways upon succeeding:
the order of your headers REALLY matters to amazon. This is not evident in their documentation, which at times is contradictory.
getting to know HTML5's FormData object was crucial for appending our policy to the payload.
Ultimately, we reverted to using Amazon's SDK and piping uploads from the client through our server on their way to s3. Solving this problem with our web form was not really going to be sufficient because our client is an Angular / Ionic / Cordova mobile app, and this simple upload form was going to require some refactoring and trial-and-error that would cost us precious time that we didn't have. With a little more time, we can change that and the client will retrieve our dynamically generated, signed-policy that will give it temporary credentials for direct upload to our S3 bucket.
Hopefully someone will find this post some day in the future that is soon enough for it to be helpful - before Amazon changes their API.
]]>Anyway, let's review. I'll be starting with some "toy problems", just like we start every day at Hack Reactor. I will only be writing about code and not showing actual code. I do not have permission to share the class's material and, even though these are common toy problems, I do not feel comfortable publishing the exact answers. Also, writing about the solutions is my preferred mechanism for review to reinforce my comprehension. I understand that it may be virtually useless to anybody else.
Before attending HR, I made it a point to get a grasp on recursive solutions to problems. I had read in many places that the mark of someone who has potential has a programmer is that they must be able to understand and intuit recursive programming.
A recursive algorithm essentially invokes itself repeatedly until it hits some base case that will make it stop and, sometimes, return it's whole trace of calculated results from the bottom of the stack and propagate them up to the top. Sometimes, while it recurses, it is storing results in some data structure outside of itself that is held in closure scope (in javascript). Programmers call this a side-effect.
The hallmark of a problem that can benefit from a recursive solution is that it is made up of many small instances of the same, larger problem. By calling itself enough times to solve the large problem, it provides a bulletproof set of instructions that can naturally adapt to the size of its input without any additional logic.
When I finally felt like I had a grasp on imagining recursive solutions, my confidence as a programmer improved noticeably. Still, there is often many more than one recursive solution to a problem, and at HR, I've come to learn how the feeling that comes with conjuring my first idea for a solution is, sometimes, a liability. In fact, algorithms that depend on using recursion to iterate are often times less efficient than other iterative approaches. When I began to understand that, I started to see how humility was going to play a great role in my ability to program from a practical angle. Sure, the seeming-sophistication of recursion is more obvious, because it wraps its logic inside of what, for me, essentially amounts to a leap of faith. It's cleaner, shorter, and just-plain-cooler to solve things with as few instructions as needed. But it's not always efficient to computation. What matters most, I've learned, is that a solution is judged on its time and space complexity, first and foremost.
This week I began to see some tricks that produce non-trivial improvements in basically the same amount of code. Some examples:
A function that applies a truth test to the leaves of a tree that traverses breadth-first by using a for-loop inside of a while loop. The leaves are are pushed into to a queue in the order of their siblings - i.e. their children are not visited until all siblings have been. The while loop iterates through the queue, so as long as there is a leaf in the queue, the function will run. What makes this better than a recursive solution? Often times, a recursive solution can require making a copy of its input at every stage so that it doesn't mutate the original input, which other recursive calls may need to operate on in its original state. Comparatively, that means that this looping solution requires far less space.
This was also a great opportunity to learn a valuable precept of breadth-first tree traversals. In the future, I now know that this kind of problem usually calls for the use of a queue. When traversing an entire tree, the time complexity is limited to O(n) at best. A solution requiring more time than that is less efficient and can be ruled out.
In another problem, we were given an array of size n and asked to calculate the greatest sum of any of its contiguous numbers. The solution must account for the presence of negative numbers. I considered many approaches to this and hoped to land on a recursive solution. What I found is that this would require a lot of space and there was no need to mutate the input or make any copies. By using a for-loop within a for-loop, we can conduct an iteration through the array that starts on index 0, and then repeats itself while i < the length of the array. Each time it repeats, the iteration will begin one index further into the array. We hold the first index as the maximum sum and calculate a new sum that is specific to this round of iteration. When this new sum becomes greater than our max sum, it takes the place of the max sum. Each time the outer for-loop iterates, the new sum is reset to 0 and will begin comparing itself against the last held maximum sum. The time complexity is n squared - an iteration through the array on each step of another iteration of size n.
In this problem, we are given two hashes of key-value pairs: one with the integers as keys 0-20 (increasing by ones) and 20-90 (increasing by tens) whose values are strings holding their english equivalent.
A second hash features the same pattern for the values ten, hundred, thousand and then increasing up through quintillion with each succeeding value multiplied by 1000.
This problem uses a recursive feature when calling itself on the remainders of modulus calculations. Those remainders are parsed by a step that accounts for numbers 1 - 100. It first checks for a number's presence in the first hash table, and if not, it will divide it by ten and call itself on the remainder.
Excepting the cases between 100 and 1000, the rest is done fairly easily.
calculate a value for the number's current place (1000, 10000, etc...) by multiplying the initial place (1000) by 1000 while the result is less than or equal to the input number.
divide the number by it's current place and round to the nearest integer.
calculate modulus for the remainder when the number and place don't evenly divide.
create a string that is the concatenation of calling itself on the result of the first division and a lookup of the current place in the second hash (words like thousand, million, etc.);
finally, invoke the method on the result of the calculation for the remainder (if the remainder wasn't zero). Concatenate this to the end of the string.
I am unsure of the time complexity of this algorithm because variance in the number of steps is dependent on many qualities of the input, but not its size (always 1).
In this problem we are asked to implement the method on the function prototype called 'bind.' This method operates on the function that call it, binds the current context to future calls of the function and applies arguments as parameters in those subsequent calls. The tricky part is that it must accept a function as future context and be able to call that function on new arguments.
My solution came fairly easily, slicing the the arguments object and treating the first item as a function used as context for future calls. Other slices then deplete the arguments object into the arguments that need to be called on its first call. The function must return a function that applies its initial input on new arguments that will be passed in. While the solution I arrived at didn't seem to have any special need to adjust for time or space complexity, since it didn't require more than a few steps, I was wrong. I planned to separate my arguments with the Array prototype's slice method. Bad idea. While both the slice method and shift/unshift methods require an iteration through the entire length of the array in order to re-index its values, a much more efficient solution is found when considering space complexity. The slice method, by design, creates a copy of the array it is called on, and this is not needed here. The shift and unshift methods mutate the array in its place and require no more space than was initially designated. This isn't all that important when implementing a function like the one we are discussing, but it illustrates well how small decisions can potentially have largely different implications to your application, despite arriving at the same results in the same amount of time.
Let's discuss a problem that I found challenging to solve, before even considering time or space complexity.
We are given the values of a number of currencies and are asked that given an arbitrary value for our input, we can count all possible combinations of these coins to arrive at that value. The solutions can contain any number of coins.
An important lesson I learned from the solution lecture for this problem: when overwhelmed by a problem that looks to be solved by recursion, consider visualizing the permutations with a much smaller input set. By observing the pattern, your instructions may reveal themselves.
Lastly, an algorithm to give us all possible permutations of words formed by telephone-number input (of n length, where numbers never change places, but we consider each possible letter that can be represented by each digit). I managed to solve this using a method I frequently try on first approach for solutions that are based on finding permutations. That method usually involves starting with a large structure or number and making it become smaller on each subsequent recursive call. It also involves a usually empty object at the beginning that will accumulate/concatenate results on each subsequent call, eventually being large enough to hit the base case and end the recursive calls. My solution to this worked well but was pretty verbose. The solution presented in class was far more elegant.
an array to store our results is instantiated.
our input number is split into an array of single integers.
we define a recursive function that will take two inputs - the word (which at the beginning is an empty string), and an index (which will increase as we iterate down the array of integers).
the base case will stop the recursive calls when the length of our word is as long as our input's length (n).
we are given a hash of keys 0-9 with their values being the possible letters to match each digit. we hold the string of 3-4 possible letters that corresponds with the digit we are currently operating on (determined by the index, starting at 0).
A for-loop will iterate through each possible letter for the digit at this index.
we call the function inside of the for-loop, passing it the result of the word that we passed in at the beginning concatenating with the letter that is currently identified by the for-loop. the second argument is our index, and we increment it by 1 as we pass it, which will make the next set of calls to all possible letters for the digit that is next in line.
lastly, return the array in which we stored our results.
Forget it, just google 'recursion' and have yourself a good laugh. It may take a second to reveal itself, so read the results carefully.
]]>So, I suppose that a depth-first log of my life's binary-tree data structure has officially found its longest path to a tree node with no children. It's not THAT crappy of an analogy.
Meanwhile, I am having an unforgettably special experience here in San Francisco at Hack Reactor. Since my arrival just 8 days ago, every moment has consumed me with thoughts about code, and I feel insulated from distraction with my curiosity nurtured. The challenges from day to day are myriad, from the concepts and problem solving, to squeezing out any of the moments for ordinary needs that the day can allow. I can see quite lucidly that my mind is really thriving at this pace, and I feel like I'm among people who have the same interest in learning. To be honest, I am stunned to be in the company of all of the brilliant minds in this cohort of 32 students. Our instructors are superb, the lectures are extremely engaging and thought-provoking, and it all makes for a tremendous environment to gain understanding of concepts that were difficult to grasp without much context. It's really, really great. Every single one of my expectations have been met here, and I'm stupidly fortunate for this opportunity.
most of my things are gone and life at hack reactor is fucking sweet.
]]>functional: a single function that instantiates an empty instance of an object that we will store data on. we will access the data with functions that we store as properties on the object - aka methods.
var dude = function(name, currentCity, food){
var newHacker = {};
newHacker.name = name;
newHacker.currentCity = currentCity;
newHacker.favoriteFood = food;
newHacker.eatAFood = function(){
if (newHacker.favoriteFood){
console.log(newHacker.name + " is totally chomping on some " + newHacker.favoriteFood + " in " + newHacker.currentCity + "!");
}
};
return newHacker;
}
var Adam = dude('Adam','San Francisco','Freedom Fries');
Adam.eatAFood()
Adam is totally chomping on some Freedom Fries in San Francisco!
functional-shared: using a few functions that separate concerns, we can make an object that defines properties and methods to be appended to any data store that we extend to it, thereby making the code more DRY, and allowing for fewer instances of those methods upon instantiating new objects. In the previous example, every time we would instantiate a new 'dude', we would create another instance of the 'eatAFood' method. In the following example, we can have one instance in memory of the 'eatAFood' method, even if we create 250 different people.
first, we need a function to assign the properties of one object to another. this helps when we want to share common methods with objects that have other differing properties. this is a naive implementation of the _.extend method from the must-have javascript utility library, UnderscoreJS.
var extend = function(obj1, obj2) {
for (var a in arguments){
for (var prop in arguments[a]){
obj1[prop] = arguments[a][prop];
}
}
return obj1;
};
now we need a border-line, tastefully sexist example of a few objects that get properties and methods from other objects on instantiation:
var peep = function(name, gender, currentCity, food){
var peepHacker = {};
peepHacker.name = name;
peepHacker.currentCity = currentCity;
peepHacker.favoriteFood = food;
if (gender === "Male"){
extend(peepHacker, dudeHacker);
} else if (gender === "Female"){
extend(peepHacker, chickHacker);
}
extend(peepHacker, peopleMethods);
return peepHacker;
};
var dudeHacker = {};
dudeHacker.gender = 'dude';
dudeHacker.hobby = 'wearing a hoodie'
var chickHacker = {};
chickHacker.gender = 'chick';
chickHacker.hobby = 'buying shoes'
var peopleMethods = {};
peopleMethods.eatAFood = function(){
if (this.favoriteFood){
console.log("This " + this.gender + " " + this.name + " is totally chewing on some " + this.favoriteFood + " in " + this.currentCity + " while " + this.hobby + "!");
}
return true;
};
var Adam = peep('Adam','Male','San Francisco','freedom fries');
var KatyPerry = peep('Katy','Female','Los Angeles','carrots');
Adam.eatAFood();
This dude Adam is totally chewing on some freedom fries in San Francisco while wearing a hoodie!
KatyPerry.eatAFood();
This chick Katy is totally chewing on some carrots in Los Angeles while buying shoes!
prototypal: a function that uses the Object.create() method to instantiate the new data structure. we pass it an object for data store that was defined outside of the function. We also previously defined the methods of that data store object as it's properties, and did so outside of the function.
var makePeepHacker = function(name, gender, currentCity, food, hobby){
var peep = Object.create(peepHacker(name, gender, currentCity, food, hobby));
return peep;
};
var peepHacker = function(name, gender, currentCity, food, hobby){
var hacker = Object.create(peopleMethods);
hacker.name = name;
hacker.currentCity = currentCity;
hacker.favoriteFood = food;
hacker.gender = gender
hacker.hobby = hobby;
return hacker;
}
var peopleMethods = {};
peopleMethods.isDoing = function(){
if (this.favoriteFood){
console.log("This " + this.gender + " " + this.name + " is totally chewing on some " + this.favoriteFood + " in " + this.currentCity + " while " + this.hobby + "!");
}
return true;
};
var Chris = makePeepHacker('Chris Christie','dude','Trenton','taylor ham, egg and cheese','causing a traffic jam');
var POTUS = makePeepHacker('Barak Obama','dude','Washington, D.C.','chili dogs','botching an epic site launch');
Chris.isDoing();
This dude Chris Christie is totally chewing on some taylor ham, egg and cheese in Trenton while causing a traffic jam!
POTUS.isDoing();
This dude Barak Obama is totally chewing on some chili dogs in Washington, D.C. while botching an epic site launch!
pseudoclassical: a function that is named for the class that it creates, and assigns its storage object and data access properties to 'this'. We then define more data access methods as properties of this class's prototype. So, calling the original function will effectively instantiate as a new object with these properties.
var Peep = function(name, species, currentCity, activity, hobby){
this._name = name;
this._species = species;
this._currentCity = currentCity;
this._activity = activity;
this._hobby = hobby;
};
Peep.prototype.isDoing = function(){
console.log("This " + this._species + " " + this._name + " is totally " + this._activity + " in " + this._currentCity + " while " + this._hobby + "!");
return true;
};
Peep.prototype.shoutName = function(){
console.log(this._name.toUpperCase() + "!!!!!!");
};
var Kanye = new Peep('Yeezy','monster','chi-town','spitting a new verse','improving upon his own self-worship');
Kanye.isDoing();
This monster Yeezy is totally spitting a new verse in chi-town while improving upon his own self-worship!
Kanye.shoutName();
YEEZY!!!!!!
]]>after this we began learning about stacks and the instantiation styles of data stuctures, while also exploring the red-green-refactor approach to writing code (aka Test Driven Development). In this, we will write tests for what we want our application to do, and we will write them to fail until we write the code that fixes them.
]]>