What do you want to learn?
Skip to main content
by Craig Shoemaker
Start CourseBookmarkAdd to Channel
Table of contents
Hello and welcome to Underscore.js Fundamentals on Pluralsight. This is Craig Shoemaker and during this course, I'll take you step-by-step through the Underscore library. So that when our time together is complete, you'll be ready to use Underscore in everyday development scenarios. First, I'll take you on a sightseeing tour through the vast Underscore API, where I'll show you dozens of code samples where you can see Underscore in action. Along the way, we'll spend some time uncovering some of the concepts and algorithms supported by Underscore and finish up with some integration exercises where you'll see how Underscore is used in some real-life situations. Well, thanks again for joining me. Let's go ahead and get started.
What is Underscore.js?
Supported Environments and Libraries
How to get Underscore.js
Now, before we get started with the demos, there's a few housekeeping things I want to go over with you. Like, how do you get Underscore.js to begin with? So, the first stop obviously is the website, underscore.js.org. From there you can go to the Downloads section and as it says here, you can Right-click and "Save As" in order to get the Development and the Production Versions. As you dig into the Development Version, there are a lot of comments in here for you. So, whether you have the website available to you in order to have the API documentation or if you just have the Underscore Development Version, both will be a great resource to you as you're learning Underscore. Obviously there's the Production Version. You can see that the Minified, Gzipped version of it is only 4KB. You can also go over to GitHub where you can have access to the full source code, but something that's even more interesting if you see the link to the tests that it has, and there's 575 tests all available, you can dig into those tests here on GitHub. And you'll notice here if we go into collections, you can see each one of the tests that are shown on the screen there for the each function. Again, another great resource for you to use as you're learning Underscore.js.
The map function is used to iterate over an array and transform that array into another array. Here, let's Run the code for this first example and I can show you. So, when the code runs you can see that Craig, John, Dan, and Elijah are all mapped from an array. So, by calling map and passing in names, the iterator function is called for each item in the array. Notice the return statement. What's happening is that the map function is creating a new array from the values that are returned from the iterator. So if you need to transform an array into a different one, map is the way you want to do it. You could even do this more with a declarative, functional-style as well, by not using the anonymous function as the iterator. So, when you go over to Pass Function, you can see that I have the author's array and then I've created a function called greet. That returns Hello plus the name. And then when I call map I'm passing in authors and I'm passing in the iterator of greet. And so what's created out of that is an entirely new array of greetings. Then from there I can call each, passing in the greetings array, and telling it to log each one. So by using this type of style, it's very easy to read exactly what's going on within the code. I'm mapping the authors through the greet function and then I'm iterating each one of the new items in my greetings array to the log. Now, if you notice at the top you can see map and it also has an alias of collect. The each function had an alias of forEach. So here in the next example, I'll show you how to use that alias and how you'll get the identical results by using the alias. So let me Run this first and you can see that Craig spent $100 in California, John spent $200 in Florida, Dan spent the most in Arizona, and Elijah spent some money in Tennessee and I think this is for the gummy bear budget. But, I'll start off by having a data object, which has a nested array of people. Then, down a little further, you can see that an object is declared called OrderItem, that it takes in a person. So when getSummary is called, it returns this string here based off of the data that's passed into it. So then when you go down and call collect data.people is passed in as the data source or as the input for the function. And the iterator goes through and creates a new instance of the OrderItem object, passing in the value. So then, the result of the m variable is an array of OrderItem objects. Then from there, each one of those are iterated over using the each function and then calling getSummary and logging that out to the results pane. So, the value of map is to be able to take an existing array and create some sort of transformation or some sort of different result based off of that array. You may make some minor changes to existing data or you might do like what I did here and create entirely new objects or just print out simple strings. It's all up to you.
The reduce function takes an array and produces a single result from that array. So here with this first code sample you'll see that I'm summing up all the values within my values array. I do that by calling reduce, passing in the values, and then setting up my iterator to decide what I'll be doing with those values. So here I'm doing sum, but you could do anything you want within the iterator. Let me Run this real quick. Now you'll notice that the signature for the iterator has memo and then the number. This function uses memoization so let me speak for a moment about what memoization is. The word memoization comes from the same root as memorandum. So, the idea being expressed here is the function is trying to remember something that has already happened, for instance the calculation of a value. When a function is memoized, the first execution runs like normal and the logic inside the function is run and it returns a result. This result is cached for subsequent calls to the function. The next time the function is run, with the same inputs, rather than executing the logic in the function again the cached result is returned. This technique is used to speed performance of a given set of logic or simply to remember previously calculated results, which is exactly what's happening right here with reduce. As you can see over in the results pane, the list items show what's being calculated as the function is run. So in the beginning, the first value that's placed into the memo argument by reduce is the first value within the array. So it says Calculating 100 + 200 and that returns 300. So then, 300 is placed into memo and so the next time it runs, memo plus the last value of 100 equals 400. So the memoization of reduce allows it to keep track of the last results, which is then passed back into the function. And in this case, it's the running total, which makes it easy to sum up the values within this array. Now, reduce doesn't always have to pass in the first value in the array as the initial value of memo and so this next example shows you how that works. Here I have an object with an array of people and so when I want to create the final value of total price, I use reduce by passing in data.people and then I have my iterator function, which again takes in memo and then value. The value in this case is an object in my array so I need to do value.price, but you'll notice that by passing in this context, the integer 0 here, memo starts at 0. I do this because I'm not working with just an integer array, but I'm working with an object array. So, I need to see it with a specific value so that the math works out correctly. So the 1st pass, 0 + 100 = 100 and then it goes on and on by keeping track of the last result. Now, you may want the results of reduce to be a little more sophisticated than just a single value. So here, I show how you can return an anonymous object. So here the data's the same, I've got my data object with the people array. I'm calling reduce by passing in data.people and the iterator is set up the same way as well, I've got memo and value. But, instead of just returning a single value, I'm returning an object that has a key of price and the value is memo.price + value.price. So, this keeps a running total of all the prices within my array. And then when I go to use it, I can use total.price to show the result. So there's quite a bit of flexibility of how you use reduce. Now I showed you how to do totals and summation of values, but again the concept is taking an array, boiling it down to a single value, and however you want to process that is completely up to you.
Now reduceRight works just like reduce, except it begins processing the array from the end of the array instead of the beginning. So you can see the result here that's produced from the productIDs array, are the values that are found in the right-most part of the array if you were to string it together in a single line, all the way over to the values at the left-most side of the array. So reduceRight works exactly the same way as reduce except it just starts at the opposite direction. So here I create the productIDList by calling reduceRight and passing in the productIDs. The iterator here has a memo and the value and at this point, I'm just processing it a little bit differently in order to create a flattened list of this multidimensional array to give me a full product ID list. Now you can do the same type of thing with Objects as well. Here I have an array of products and I've just got the product IDs in here. I'm calling reduceRight by passing in the products and then inside the iterator I'm keeping a full list of the product IDs by concatenating on the latest product's ID by using val.id. Once I have the full list, then I can log that out to the results window as well.
Now the find function allows you to find the first value based off of your criteria within your array. So, I've implemented this demo using a little more of a functional style, by giving certain criteria functions that I can pass in to find, in order to return the result that I'm looking for. So here I have my values, an integer array from 1 to 10 and then I've got evenCriteria where I take a look at the modulus and if that's equal to 0, then I know I have an even number, oddCriteria is the inverse of that. My greaterThanEightCriteria, which looks at the value and sees if it's greater than 8 and lessThanFiveCriteria does well, pretty much the same thing except it's looking to see if it's less than 5. So here, I can log whether or not I'm finding the first even number by calling find, passing in the values, and then passing in the evenCriteria. So in this case, evenCriteria up here, acts as the iterator for the find function that it's applying against the values, and the same thing for oddCriteria and the other two that I've set up. So, you could very easily have functions in some sort of common library that define data that you need to find within your array. But notice, that the find function only finds the first occurrence of the data that you're looking for based off of the criteria. There's another function that will find everything you're looking for, but find just finds the first instance of it. If we're working with an object array, things are much the same. So here I have my values, an array of people here, Craig, John, Dan, and Elijah and we have state and price. So evenCriteria, oddCriteria, greaterThanEight and Five, on and on. Notice how the value that's coming in is the instance of the object, not just the numerical value. So, I need to do value.price and then I can apply the same type of logic in these criteria functions as I did before. So again, I can take a look at the values. I can call find, pass in the values, pass in the evenCriteria. That will return a single object so then I can do .price in order to get the value that I'm looking for. So find is the function you want to use if you want to find the first occurrence of data given the criteria that you pass to it.
Now the filter function allows you to, surprisingly, filter out some values based off of criteria that you pass it. And I think you'll notice that some of these criteria look kind of familiar from the last demo that we did. So here I've got my values, which is an integer array of 1 to 10, even, odd, greaterThanEight, and lessThanFive criteria are all defined as functions. Then I can call filter, pass in the values, and give it the criteria that I'm looking for. So, what's interesting is that you can create, like I said, a library of these different criteria functions and use them in many different cases. So, here you can see I've filtered out all of the values that do not match my criteria, given what I'm looking for. So, I have a list of Even numbers, Odd numbers, Numbers greater than 8, and less than 5. That works great on an Integer Array. Now let's try it on an Object Array. So here I have my array of values and you can see I've got name, state, and price and I have basically the same type of criteria functions as I had in the last demo. The main difference here is that since I'm working with objects, I need to know which property of the object to look at in order to evaluate the criteria. So notice down in evenCriteria, you can see that I'm going to value.price rather than just the value that's coming into the function. Beyond that, everything else is the same. It's the same logic that I have in these functions as I did in the other one. So now when I go to call filter, I pass in the values and I can pass in my criteria functions and then at that point I get back the list of filtered items and then I can iterate through each one using the each function.
Now, if the find function finds the first match based off the criteria that you send it, the where function will find all the matches based off the criteria you send into the function. So, with my data here you can see that I've got a number of different courses. I've got some by Dan Wahlin. A couple of them here are on different levels so I've one Intermediate, a Beginner, and another Beginner. I have some by Craig Shoemaker, John Papa, Elijah Manor. Notice, different authors in different levels are all within this object array. So, I'll run this and you can see that when I'm trying to find the Intermediate Courses, I call the where function and pass in the courses and then add in an object that has the key and the value that I need to match on the data that it's querying. So here it'll find all the courses where the level is equal to Intermediate. Now, I can get more specific than that as well. If I want to find all of Dan's Beginner Courses, that's just as easy as building up an object where I have the key of author and the value of Dan Wahlin, the key of level and the value of Beginner. Then when I pass that to where, I get back an array of matching objects that adhere to the criteria that I sent into the where function. Now sometimes it's easy to confuse find and where, so just remember that when you're looking for all of the matching items, you're looking for data that matches a where clause, like if you were doing it in SQL. So, hopefully that just gives you a little key to remember which function to use and when.
Now, a find locates for you the first occurrence of the data that matches your criteria and where takes in an object literal, describing the criteria instead of a function like find does. findWhere brings those two together. So, let's take a look at the data for a second. I've got that same list of courses and now what I want to do is find the Intermediate Courses and also Dan's Beginner Courses. So, instead of having to implement a criteria function I can just pass in the object. Again with the key and the value matching what you want to find, whether I have a single value, like if I'm looking for Intermediate or if I have something that's a little more verbose by looking at author and level, findWhere will find the first occurrence of whatever you're looking for and return it to you.
Reject is basically the opposite of filter. So, given a certain type of criteria, you're going to get the values back that are not within the criteria that you pass in. So I've got my integer values and I've got the same criteria functions that I've had in the past and when I call reject, you can see that when I pass in the evenCriteria, I get back numbers that are not even. When I pass in the oddCriteria, I get back the even numbers or the numbers that are not odd and on down the line. The criteria functions change just a little bit when you're working with objects. Again, instead of the raw value coming in, you need to take a look at the object and find the member that you're looking to evaluate, but the concept is the same. Reject will return the values that don't match the criteria that you pass in.
Where the every function looks at the incoming values to see if every single one of them passes the truth test, the some function looks at the values to see if any of them passed the truth test. So, similar to how we had it before, I'm using the same type of evaluation and the same data except I'm using the some function instead of the every function. So here, 'Are some values "truthy"?' Well, they all are because they each have a value. Are they truthy using type coercion? Yes they are as well, because although null will evaluate to false, all the other ones do so some of them are true. Are some of the values actually true? Yes, the first one in the array is. So let's change the data up a little bit and you can see that each one has a value, type coercion returns true, and the first one is actually true once again. And we get similar results by making the entire array filled with true values. Now, let's take a look at that integer array again. I want to know if some of the values are truthy. Well again, using this criteria they all are. I want to know if some of them are true using type coercion while the number one is. Are some of the values true? Well no, none of them are actually true, because they're all integers within the array. Then I can find out by passing in criteria if some of them meet the criteria. So, are some of them even? Yes, and are some of them odd? Yes. Now, if we take a look an Object Array, we can also find out if any of the records that we have in our array have a value for state. So, hasStateCriteria looks to see if state does not equal an empty string. And here, I'm using the any alias instead of some and asking if the values, if any of them, have the state criteria. And since most of them evaluate to true, the final result is true.
The contains function or include, looks to see if the array or the object contains a specific value. So here, I can look to see if the array contains a null value and it does within the third position. The number 1 is in the second position. The string of 'yes' is in the last position, but it doesn't contain any false Booleans. So, calling include, passing in values, and then passing in the false value will return false. So the way contains works is you pass it the values you want to look at and then the actual value that you want it to evaluate against. So, if we take a look at an object, you can see that here I have an object of author, firstName of Craig, lastName of Shoemaker. Here I want to see if the author contains the value of Craig and this does return true, because firstName is equal to Craig.
The purpose of invoke is to call a method on each item within an array. So here, what I can do is invoke the sort method on each one of the arrays that are passed in to the invoke function. So when I run this, you can see that each one of the arrays are sorted because right here, when I call invoke I'm passing the method name of sort. So, as it goes through each one of those items, it runs that method and returns back the resulting values. Now, if you add Extra Parameters to the invoke statement, so notice here, I've got the same arrays, but now I'm calling join instead of sort, but I also have another parameter, which will be passed in to join as the pipe character. So now, when I run this, the arrays are flattened and delimited with the pipe. When I'm working with an Object Array, you can see that the getSummary method is being called on each one of the items in the array. So here, I have an object definition here where I'm passing in a person and I can get the summary. So I have an items array that's created and I push in a new item here for Craig, who lives in California and spent $100. Then I've got John who lives in Florida and he spent $200. So the results is a new array that's filled up with the result of invoking that method on each one of the items and getting the results. So each time it goes through an item within the items collection it calls getSummary. And the string that's returned from getSummary, which you can see up here, is the message that's printed out on the screen, is what's filled up in the results array. Once I have that, I can log the results. And notice that if you try to invoke a method that doesn't exist, you'll get a TypeError.
The pluck function is used to extract out the values from an array, given a certain key or property name. So, you'll notice here I've got an array of authors and what I want to do is just get the author names. So when I Run this, I get John and Dan returned into the results array. Now you can also do this with some more complex-type of objects as well. So here, I've expanded the author's array so I have the name and state and also the courses that they've done. So John has a couple courses and Dan has a couple courses. So this time when I pluck out courses, it fills up an array with all of the array values that came from the original objects.
Max and Min
The max function calculates the maximum value in a collection. When evaluated against an array all you have to do is pass the array into the function in order to get the maximum value. When calculating max against an Object Array, you need to create an iterator function that returns an object property that you want used in the calculation of the max value. In this case, I'm returning author.budget all to find out who has the largest gummy bear budget. Just like the max function, the min function calculates the minimum value in a collection. When evaluated against an array, again you just pass in the array and it figures out the minimum value. When working with an Object Array, you just do the same thing with the iterator. Here I'm returning again author.budget and you can see that unfortunately, John has the smallest budget for buying gummy bears.
The sortBy function again works with objects and arrays. When we take a look at the Integer Array here, at first this might seem kind of odd because array has a sort method on it. But, using the sortBy function allows you to take more control over how the sorting happens. For instance, when you take a look at the sort criteria you can see I'm evaluating whether or not I have even numbers or not. So here, I can sort the numbers in my array by even numbers first and then odd numbers. So using sortBy gives you a lot of control over how you manage your data. If I take a look at a String Array, again, if I just call sort on the array itself it'll sort it alphabetical. But maybe I want to sort the names by length of name and here I can do that very easy by implementing the correct iterator function. When I'm working with Objects, there are two options you have available to you. Notice down here sortBy, I'm passing in authors and I just have a string that says which property on the objects I want to sort by. So when I run this, it sorts it alphabetically by name. But at the same time, I can go over and implement an iterator so that I can do something perhaps a little more complex. So let's take a look at the data here for a moment. I have the author's array, which has author objects in here for John, Dan, Elijah, and Craig. Now, I also have another array that came from say some other source that has the SortOrder for this array. So, this is set up so that the SortOrder value matches the data and the authors based off of its index array. So if we come down to sortCriteria you can see that it's returning the value from authorSortOrder based off of the index that's being passed into it. So now, when I call sortBy I pass in the authors, pass in sortCriteria, and then the sorted authors are returned from that. Then at that point, I can just log out the series of sorted authors. And you'll see here that based off of the index values in authorSortOrder the data is being shown in exactly that order.
The countBy function works in a very similar way to the groupBy function. If I take a look at the Integer Array, you see I have the same array of values. I also have the same groupBy criteria. So, I'm grouping by absolute value of the numbers, but this time instead of getting back the values in groups, I'm getting back the counts in groups. So you can see that there's 3 values in the group named 1, 1 in 2, 2 in 3, and so on. It operates the same way as groupBy does in that the result of countBy is an object where the keys are whatever you created within the groupCriteria. So I can use the same approach to extract the keys, iterate over those keys, and use that to reach in to the countedValues in order to extract out exactly what the counts are for each group. Also like groupBy, I can do the same thing with property name of an object. So here I have my list of authors and I want to count how many live in different states. So when I call countBy, I can pass in authors and then pass in the string for state. Once I have the countedAuthors then I can extract out the keys, iterate over the keys, and at that point, I know how to handle the groups. So here I've got some logic that kind of deals with the pluralization when there's more than one author in a state and then I can just log the value, which comes out of countedAuthors, by passing in the key. And remember this is the same things as doing countedAuthors.fl for Florida, in the case of the first item in the array. And then I create up the rest of the message and print it out on the page. You can also do it with the groupBy criteria. So here I can get the count of authors that live in coastal or landlocked states. I've got the same array of authors and the countedAuthors is generated by calling countBy, passing in the authors, and then the groupBy criteria. Again, it just returns the object that has the keys that match whatever criteria you're using in order to do the groupings.
When you take a look at the Underscore documentation, it says that the shuffle function uses the Fisher-Yates Shuffle Algorithm. The Fisher-Yates Shuffle Algorithm, also known as Knuth shuffle, is an algorithm that is used to create a randomized set of values without repetition. Consider for a moment that you have 10 songs on your iPod that you want shuffled. Now with a truly random shuffle, the last song you just heard could just as easily be the next song on the list, unless you have some way of marking that song as being ineligible for being an option as the next item in the shuffle sequence. What you really want is a way to say that, as soon as that song is being played, it's no longer in the set of possible values, even when the next value is being determined randomly. The Fisher-Yates Shuffle does exactly this. As each item in the shuffle is determined, it's no longer an option or is marked off the list for being picked in the next random selection of values. And you get all this functionality very simply by using the Underscore shuffle function. Now back to the demos. When working with an Integer Array, all you have to do is pass it to the shuffle function and so each time I Run this I'll get a different shuffle of those same values. And you'll notice there's no repetition going on in any of the series. The string array is the same thing. All I have to do is keep hitting shuffle and I get a different order each time, no repetition. Now what happens if I have a Mixed Array? Well, actually, it works on that just as well. Perhaps a multidimensional array? Now, the way that my log function works, it's taking a look at the nested arrays and putting them in individual list items, but what's being returned from shuffle is a shuffled list as another multidimensional array. So, shuffle isn't transforming the data as it's coming through, but my log function flattens it all out. What about an Object Array? Same thing here, each one of the items is being shuffled all along the way.
As the Underscore documentation states, the toArray function is often used to convert the arguments of a function into a real array. Did you know that the arguments of a function are NOT a real array? Well in fact, they are not. The arguments of a function aren't a real array because the arguments does not have any of the same properties of an array, except for length. So often, you'll find that you might want to use toArray for arguments and for other purposes as well. Here I'm creating a sum function and I'm not explicitly stating anything in the function's arguments because I'm just going to grab the arguments as they come in. You can see down below within the usage, the number of arguments can grow or shrink, depending on whatever's necessary. So, the first thing that I'll do here is call toArray against the arguments, which converts the function arguments into a regular array. Then I can do some interesting things like call reduce in order to sum up all the values within that array. So here, I can call sum, 2, and 2 or 5, 2, and 8 or even this longer series of numbers in order to get the actual value that's summed up from the argument list that's passed into the function. What about another way of using it? What about Strings? You can pass a string into toArray and you get an array of characters from that string. So you notice, once I have the values, I can say that strings are made up of individual characters. And I can grab the first item from that array by passing in index 0, which returns 1 or it can iterate through the entire array to see how they're split across. So the string 1, 2, 3, 4 creates an array with 4 positions in it with the values of 1, 2, 3, 4. What about using it on an Object? Well, if I run this you can see that it extracts out the values from the object. Like the comment says here, there's a better way to do this and when I get into the object functions you'll kind of see that in action. But, I wanted you to see this so you could know how the 2 array function operates when you pass in an object to it.
The size function works very similar to what you'd expect from the length property of an array, except this will work on objects and arrays. So when I have the array of values and I Run the size function against it, you can see because there's 4 entries in the values array. If I Run it against an object, you can see that I get 2 because there are 2 properties in the person object. And an Object Array really is no different from any other type of array, but here I wanted to show you how this would behave as well. Again, returning the size of 2, because there are 2 objects within the people array.
Introduction, First,Iinitial, Last and Rest
Underscore functions crafted to work with Arrays give you an opportunity to extract items, find overlapping data, find specialized indices, and much more. Alright, let's begin with the first function. The first function returns the first element in an array. And if you pass in an Explicit Amount as an argument, it'll take the first number of items based off of the number you pass in. So, take(authors, 2) returns the first two items within the array. Just like first, the initial function returns everything except for the last item in the array. Now, if you want that change that up a little bit and pass in a value for the last argument, it will take everything except for the last number amount that you pass in. Following the same pattern as first and initial, last will return to you the last item in an array. Now, if you pass in a value as the last argument to the function, it'll return to you the last end number of items in the array. Finally, the last function in this kind of "group" of functions is rest and rest will give you everything except the first element in the array. Of course, unless you pass in a specific index, which indicates which index to start at in order to give you the rest of the elements in the array.
The compact function takes an array and removes all falsy values from it. Well, that's what the documentation says, but there's something that I want you to be aware of. Watch when I run this. You'll notice that I have Craig, the number 1, the text One, then the value of false and an object. Let me run it one more time and stop at the break point. So, when we take a look at the values after compact has been run, you'll notice that this array here still has a false value in it. So, make sure that when you run compact, you're only expecting the falsy values to be removed from the first level of the array. In other words, if you want it to work for multidimensional arrays, you'll have to recursively go through those arrays in order to strip out the values.
The flatten function will take an array, whether it's a nested or a multidimensional array, and flatten it down into a single, flat array. So, you can see here I've got some data here about a specific author so these are topics that I've done on Pluralsight and some contact information. So, when I call flatten, all of that will be returned into a single array and you'll notice here that the flatten array length is 27 items. Just so you can see for yourself, let's take a look at it in the debugger. Here the author array is a multidimensional array with some extra values in position 0 and 1. And the flattened version is just a single array with all of those values. Now, the flatten function will work no matter what depth or how many levels down your array is. But, what if you wanted to only work on the first level of the array? If you pass in a Boolean value as the second parameter for flatten, that tells it to only do a shallow flattening of the array. So, when I Run this you can now see that the shallow array length is 11 and here's all the different values that are in that array. Again, let's take a look at it in the debugger so you can see what it looks like. Here's the original array that has 2 arrays in the 2 and 3 positions of the array, but then it has arrays even further down. So, this array, this array, and this array all will not be flattened, but the array at position 2 and position 3 will be flattened. So now when we take a look at the shallow result, you can see that the course names that were at the bottom level of the first nested array, now are flattened down into the root array and the arrays that were further nested in the array are now brought down to the root level as well. So, depending on how you want to look at the data in your array you may want to call shallow or just allow it to flatten the entire thing.
Without, Union, Intersection and Difference
The without function returns a copy of an array without values that you specify to it. So when I Run this you see that the resulting array only has Craig Shoemaker because I called without, passed in author, and said that I didn't want any of the active or 1 values in the resulting array. The union function will take in two arrays and return only the unique values between the two arrays. So, notice in the speakers array, Dan Wahlin and Craig Shoemaker appear both there and in the authors array. But when I Run this, I get a single list of only unique values between the two arrays. Intersection will look at two arrays and return a unique list of the values that show up in both arrays. So notice that Scott Guthrie is just speaking at the event and Craig Shoemaker is only an author and so when I Run this the overlapping values, John, Dan, and Elijah are the ones that show up in the resulting array. Difference takes a look at two arrays and also returns a list of unique values, but it's going to look at the first array and compare it with the second array. So, when I Run this you'll see that the values of Scott Guthrie and Martin Fowler are returned as the result, because that's the difference between the values that are in speakers and authors. But notice in authors, Craig Shoemaker shows up in authors and not in speakers. Difference returns the values that are in the first array or speakers that are not in the second array. So, you'll want to make sure you have that distinction in your mind, that it doesn't return only unique values from the two arrays. The uniq function takes a look at the incoming array and produces a result that has only unique values. So notice in this array, the number 1 and 5 show up more than once. So when I Run it and get the results, I have the values in basically the same order, just only skipping any duplicates. It works on Strings as well. Here, I have Dan and Elijah being duplicated within the array. When I call uniq on authors I get only the unique set of names in that array. Now, one of the options you have available to you is to tell uniq whether or not your data is sorted or unsorted. So, let's take a look at running this and see how fast it is in order to iterate over a set of 1,000 first names. So when I Run this you can see that unsorted it takes 4 ms. That was 1,000 names in the array. There are 33 Unique Names and here are all the Unique Names. So that took 4 ms. What if we sort it ahead of time? Here, I'm taking the firstNames, sorting them, and then when I call unique I pass in values and then pass in the true argument to say that this is already presorted. So now when I Run this, against the same amount of values, you can see that it only took 2 ms out of the same set of data. So, if you can sort your data ahead of time or you know it's already sorted, you can speed up that algorithm by quite a bit. Now, what if you want to do something that's a little more involved than just a simple compare? Here's an array of product information. Notice how this is structured, the product id, the first one and the second is same except for the color code that shows up at the end of the id. The title shows that it's the same product, but the color shows that it's just a different color of that same product. So for Widget A and Widget B, it shows up in two different colors. Well here I can implement an iterator, which is the uniqueCriteria. So as a product comes in, I can take a look at the id and split it based off of the dash delimiter. Then from there, I can return just that first part of the id. So, it'd be this part right here. Now, if for some reason that id didn't have a dash or didn't work I'm going to fall back to using the product title, but for the most part I know that will work. So now when I go to call unique I can pass in the products. I know it's not sorted right now so I'm going to go ahead and pass in false and then pass in the uniqueCriteria. And when I do that, you can see that the initial length of products was 4. The length of the custom unique's products array is 2 and there's the Unique Names that are returned. And Widget A and Widget B are the Unique Names that are returned.
Zip accepts an arbitrary number of arrays and combines all the values into the single resulting array. So, when I Run this, you can see that the single array has all the data from the three arrays in it. But, let's also take a look at it in the debugger to see exactly how that works. So I'm passing authors, states, and courses, which are all flat arrays into zip. The values that are returned is a multidimensional array where all the values that are in the 0 index position of the incoming arrays show up in an array in the 0 index position of the final result. And all the values that show up in the 1 index position of the array, show up in the 1 index position of the results. So now let's take a look and see how it behaves with Nested Arrays. So here in have basically the same data, authors, and states except courses now is a nested array to show course names for each one of the authors. When I Run it, again I get all the information, but notice how it's organized. Since these topic names and this state name all associate with Craig, being in the first index position of those arrays, they're all associated together. Let's take a look at it in the debugger. So see, courses this time is a nested array and now when allValues comes through, index position of 0 will be Craig's information and it brings in that array from courses. So, as long as the index positions line up among data that you're getting from different sources, zip can be very useful to associate that data together.
Object takes arrays and creates an object out of it. So notice here, how I have an array for keys and values. And these will match up to the keys and values that will be in the resulting object. So, when I call object by passing in those keys and values, I get back an object where I can call author.firstName, author.state, and even author.lastName. So this is a real straightforward way to go about it. If your data comes in a little bit of a different form you can use a Nested Array in order to pass in the keys and values. So notice I have the data array and each element in the array is a key and value pair. So if I have data set up like this, I can pass it to the object function all together and I get the same object as I did before.
IndexOf, LastIndexOf and SortedIndexOf
indexOf will return the index of an item based off of the value that you provide. Now, if there's a native index of implementation available in the environment then it will defer to that. So when I look at an integer array, I can see that index 5 is at index 1 position. It can work against a String Array. Index of Elijah is 2. Now, you can also provide a start location. You can tell it where you want to begin looking. So, if I pass in authors, tell it to look for the index of Elijah, and then give it the index 3 to Run, it'll start in the index position of 3 and then return the result from the next occurrence found. So here, since I'm starting at index 3, the index of Elijah now is 4 instead of 2. If it can't find an item in the array, the index will return -1. And you can also do a Binary Search. If you know that your data is sorted it will use a faster algorithm. Now, I'm using Chrome and it has a native implementation of indexOf so this is going to go pretty quick either way. But notice, I'm bringing in the firstNames array and again, that's a list of 1,000 first names, but when I get the unique names it comes down to about 33. Then I take that list and shuffle it. So you can see the first instance, when I'm calling indexOf and passing in the shuffled list in the second instance I'm passing in a sorted list and telling indexOf that the data is presorted. If I continue to Run this, you can see that the index of Steven changes at the top, because that's the shuffled list and it remains the same for the second one because that's the sorted list. Down in the console you can see that either way it's performing pretty fast. lastIndexOf returns the last index position where a value occurs in an array. Now notice, up at the top there's the E5 icon so this falls back to native implementations when it's available. In the first example, I'm looking for the lastIndexOf 5 and since it shows up twice, I'll get the returning value of 8 because it shows up in the eighth position for the last time. When we take a look of how it works out with Strings, here Elijah shows up twice, but when I ask for the lastIndexOf you can see that here in position 4 of the array, is the value that it returns. Now just like indexOf you can provide a start location for lastIndexOf. So, when I ask for the lastIndexOf Elijah, but say start at position 3 in the array, the value is now 2. And of course if it can't find anything in the array, the return value is -1. sortedIndex returns to you the index of an item if you were to insert it into a sorted list. So the input needs to be already sorted as sortedIndex uses the binary search algorithm in order to find the final value. So, if you take a look at my Integer Array here, I have a scrambled array and it even has some repetition in it and I did that on purpose. So the first thing I'm doing is sorting the array and then when I come down to sortedIndex I pass in the values and ask what the index for the integer number 7 would be if I were to insert it in this sorted array. Now, when I Run it you can see that it lists out all the values with the associated index values that it has in the array and you can see that in order to insert the number 7, I would want to put it at position 8 and that's exactly the right spot. Let's take a look and see how it behaves with strings. Here again, I've got an array of names and I also put some repetition here on purpose. So when I call sortedIndex against authors and ask where do I need to insert Elijah, you can see that the index position is 3, even though I already have 2 entries for Elijah. So, as long as I'm sorting my data before I'm passing it into sortedIndex, I'll get the appropriate index value to insert it into if I were to add that item to the index. Now, you can use this on objects too, not just arrays. This one takes an iterator though. So here, I have my array of authors and I also have a new object that I'm creating, just for Julie. So maybe I have an existing data source and now I have a new object that I want to add in to that collection. But, I want to make sure I added it into the right position. My sorting iterator takes a look at the incoming author and returns the name property so that as the function begins to sort it's looking at the same data for each one of the objects. So now when I call sortedIndex I pass in the authors, I pass in the julie object, that has the same property as all of the other objects so it will be looking at .name for the data source, as well as the julie object. And when I Run it you can see that the index to insert Julie is at position 4, which is exactly right.
Range generates a list of numerical values and you have some flexibility on the results based off the arguments that you pass in. So, if you pass a stop value only then it will begin the range at 0 and it continues to the stop value. But notice that the stop value is not inclusive in the range. So the values here go from 0 to 5. You can also provide a Start Value, along with stop and it begins the range at the number provided. Here, there are 8 values generated going from -2 to 5 and again notice that it stops before the given stop value. Finally, you can also provide a Step value. And here the list goes from -2 to a stop value of 6 in increments by 2, but since it's not including 6, there's only 4 values as a result.
Bind and BindAll
Partial is a function that can help you prepare functions for future use. And one of the things that the documentation points out is that it doesn't affect the reference of the this pointer. When you use partial you can fill some arguments of a function ahead of time and use that function at a later time, even while defining extra arguments when you call the function. Let me show you here. This is largely the same code I used in the bindAll demo. Except now I'm creating a function called reportAuthors. In the listNames function I'm passing in a message and then I also have another parameter that decides whether or not I'll show that message. The key here is to show that the function is looking at this.authors in order to iterate over the array of author names that I defined in the view model. Now, when I create reportAuthors, I'm creating this as a partial. The partial is set up by pointing to the listNames function and then I'm passing in a value for the first argument of message and that is, 'is a Pluralsight author.' So, as I Run this, the unattached version of it shows the message because I passed in true for the second argument. It looks like it's the first argument when I call reportAuthors, but that's because partial is doing the work of passing in the value for the first argument. Now, even though I've bound it to this button for List Authors, now when I click on it it shows the names, but it doesn't show the message because, as I bind it to the click handler showMessage comes through as false. So, basically this just gives you a way to compose some of your functions in such a way that you're adding some of the information in ahead of time and you can pass those functions around and use them as needed.
Just as I said on the slides, memoizing a function helps increase the performance by not requiring the reevaluation of logic of a function given the same inputs. In this example, I'm looping over the showNumberMemozied function 10 times, twice in a row. Here, take a look at what happens. The first run evaluates the implementation in showNumber for each iteration in the loop. And you can tell that because the log, inside of showNumber, is saying this function is run for and then it gives the index value. If we take a look at the second run, this path simply returns the value as produced by showNumber. So notice the loops, there's nothing different. I'm calling showNumberMemozied in both instances. So, this can greatly increase the speed of your code and it's all done just by calling memoize and passing in your function. Then from there, you just use the new function variable anytime you want to call that function. Okay, now let's take a look at what this might do in a little more of a real-world-type of context. So here, I have the algorithm for generating a Fibonacii value. Let's open up the developer console and what we'll do is take a look and see how long it takes to run getFibonacciValue, all the way up to 30 and we'll run it twice. So here, the first run took 52 ms and the second run took 25 ms. So, that's all done without memoization. Now let's try it with memoization. So here, this is the exact same algorithm used without memoization, but now I'm calling memoize and passing in the function name. The target is still 30 and now I'll Run this again. So you can see now, once it's memoized, the first call took 22 ms. Subsequent calls to getFibonacciValueMemoized took 0 ms. And here I can continue to press on this button to Run Again, and I'll get the same result and you can see, it might take a millisecond, it might take 0 ms, but it's still much faster than what it was without memoization.
Delay, Defer and Throttle
Delay is a function that works much like the native setTimeout function, but with a little bit more flexibility. In this example once I click on the button, after a second-and-a-half, some text is rendered to the output window. A second-and-a-half after that, the shout function is called to change the text just a little bit. The key thing to remember here is that if you pass extra arguments to delay, after the function to run, and the seconds to wait, they're forwarded to the target function. So, in this case, it's sizeinEms, which is 2 and the color, which is maroon. Defer defers execution of the target function until the current call stack is cleared. Here I'm logging a message to the output window and then immediately logging another message, but this time using the defer function. Then I call getFibonacciValue, which takes a bit of computational time. Notice the order in which the results show up. First, it's the 'I am an immediate message.', then the results of the Fibonacci calculation, and finally the deferred message. So, this function can come in handy when you need to update the UI after some intense calculations or perhaps building up some markup for the page. Have you ever needed to disable a button for a time, so that users wouldn't click on it a million times? Have you ever had to put checking code in a function to make sure that consecutive calls in a short amount of time skip some of your logic? Well, if you have then throttle is just what you need. In this example the log function is throttled so that it's called one and only one time during the given 100 ms. After that time has elapsed, then it's eligible to be called again. Here, let me show you. So, when I clicked on the button it went through the loop and iterated 1,000 times. But, as you can see my throttledLog was only called once in the loop each time testThrottle was called. So, you can configure the wait time, the second parameter in the function, to be whatever you need, but this will help you control how often logic is processed within your functions.
Debounce is a function that you can use to make sure that your function is only fired after a certain amount of time has elapsed. The best way to explain this to you is just to show you. So, first what I'll do is Run the code here and what this does is this binds logDimensions to window.resize. Now, let's exit out of full screen and you'll notice, you'll see some information coming in on the results window. Now as I resize the window, every time the window moves, even just a little bit, it starts to log that information. If we take a look at the log you can see it's growing quite large. So that's just a raw attachment of my logDimensions function to window.resize. But, if I do it with debouncing, now I ran that code and it attached it there. So notice here, I'm calling debounce, calling logDimensions, and I'm saying every 250 ms is when this function can be run. So now let's do this once again and as I resize the window, kind of going along, and there, it's not a huge stream of information coming in. It's only running if it's been after 250 ms. And again, the way that's set up is I have my regular function, just like normal, but I'm passing it in to debounce and now when I want to call window.resize, instead of pointing to logDimensions I'm pointing to logDimensionsDebounced. Anybody up for some basketball?
Once and After
Well, once is pretty self-explanatory. So, here I'm wrapping a call to my log function in once and then I'm calling callOnce 1,000 times, but you'll notice when I Run this, it's only called once in the loop. So this can be really useful if you have a number of different asynchronous processes going on and you need to call some function, but you know you only need to call it once. So, whether it's from promises or callbacks or whatever you have going on, you'll probably end up finding a lot of use for this function in your pages. Now similar to once, after creates a wrapper around a function so that it will only execute after it's been called x number of times. So for instance, if I Run this, there are two buttons put up on the screen. And the way I have after set up is that depending on how many buttons there are on the screen, so I'm passing in the size of my confirmationButtons, which is two, then it can run the logConfirmation function. I'm taking the result of after and setting that into the click handler of my confirmationButtons. So when I click on Button One and then Button Two, only after I've run the confirm function twice does my result show up in the results pane. So again, there's a lot of use for something like this when you're dealing with asynchronous operations because when you don't know when responses will come in, but perhaps you know there's a certain set or a certain amount that you need in order to take the next step, this can come in really handy.
The wrap function allows you to wrap one function inside another function. And it does this by passing the first function, sent to wrap, as the first argument to the second function. So, let's take a look at this code here. First, I'm calling wrap and I'm passing in foo and then bar. So, I'll Run it to get the result. Now when bar is executed, the foo function is sent in to the first argument. So it's represented by func right here. So, when bar is called, immediately foo is called. And you can see that by it printing up foo into the result window. Then bar has some implementation and so right here it's just logging bar and then the arguments that I passed to the wrapped function are delegated over to the target function, which is bar. So, that's why argument 1 and argument 2 show up inside of bar when I've passed them here from the wrapped function. Now this isn't quite a bit different, but you'll see the different style so I wanted to show you. You can also do it with an Anonymous Function and so this is basically the same implementation except one's using the Anonymous Function and the other one was just using a named function. But even when you see it like this, just know that the arguments that are passed in to wrapped are forwarded on to the target function, which is the second parameter in the wrap function. Now, one of the things that's not quite clear from the Underscore documentation is the fact that you can wrap objects also, not just functions. So first let's take a look and see what we have here. I have a settings object that has enabled logging and warning. Then I have the notify function. Now, I've wrapped settings so that the settings are passed in to this anonymous function. So when I call it, all I'll do is send in a message. And then based off the settings, it'll decide whether or not to do a console.log or console.warn. And then either way it'll log the message to the output window. So then down at the bottom I can call notify and pass in a message, 'Inventory critically low!' and I'll Run it. Now when we take a look at the console, you can see that the console.log and the console.warn ran, because the settings were being passed into it. So now, just to be thorough, let's go ahead and take a look at how you use it with arrays. And so, it's the same concept. Here's my values array and I'm wrapping it with the wrap function to the target for logLength. Now, I didn't declare any other arguments, but again if I did have them in this function, I could pass them in through the wrapped function. So when we Run it, you can see that the array's length is 10.
The compose function takes a list of functions and each function consumes the return value of the function that follows. Let me scroll down here and give you an idea of what that means. So the first function that will be run is funcThree. The result of funcThree will be passed in as a parameter to funcTwo. Then funcTwo will run. The result of funcTwo will be passed in as a parameter to funcOne. Finally, funcOne will finish executing and it will return a value. To see this all working, let's open up the debugger. So, to make the call I'm calling composition and passing in argument 1 and argument 2. The last function that's in the chain passed to compose, is the one that will receive the arguments that are given to the function that's the result of calling of compose. So, argument 1 and argument 2 will only be passed to funcThree. So, when I Run this you can see that here's funcThree and the arguments that are passed in are argument 1 and argument 2. So, it'll step through and do its thing. So now that funcThree is done executing, it will run funcTwo. So here we are at funcTwo and you can see that the previous value is that funcThree is complete because funcThree returned the message of funcThree is complete. FuncTwo will continue to execute and when it's done, it will return funcTwo is complete. So now it makes its way up to funcOne. The previous value here in the arguments is funcTwo is complete and it executes and finally returns a value. So you can see, up in the output window how the composition is happening. funcThree is run, it's able to process the arguments, it returns its value to funcTwo, and you can see right here that funcThree is complete, it's printed up because it was available in the arguments. The same thing for funcOne, having the return value from funcTwo in its arguments list. And here, I'm able to take the final result and log it out to the output window. So this could be very powerful in giving you the ability to compose together a number of different functions that perhaps do math calculations, manipulate the DOM or anything where the return value of one function could be used for the next function in the composition.
Chaining and Objects
Using the Object Oriented Approach
Now, I touched on this a little bit during the slides of the introduction, but there's a feature of Underscore that allows you to call functions either in a functional style or using more of an object-oriented style. Here I'm calling the each function, which I'm sure you're very familiar with by now, against the authors array. And so, by placing authors in the parentheses, I can simply call .each, pass in an iterator and a context. So running this code gives me the exact same output that I would have from just using the traditional approach of underscore, dot, function name, and then passing in the object or array that the function works with. So basically what's happening is that the input data is being passed in as the first argument to the function that you're calling. So, any other arguments that you would pass in to that function just come directly after calling the function. Now let's take a look at a different example and it's exactly the same for any function that you would call within Underscore. But here I have my array of author names and if I want to extract out the first name or if I want to extract out the last name in the list, I can do that just by calling underscore, parenthesis, the input, close parenthesis, and whatever function I'm calling. So again, it works the same way for any function that you'd find in Underscore.
Chain and Tap
The next bit of syntactic sugar is chain. What chain allows you to do is chain together a number of different functions in Underscore to finally end up with the resulting value. So here I've an array of product records and what I want to try and do is find the highest price of the item and I can accomplish that by chaining together a number of different Underscore functions. So, in order to find the most expensive what I'll do is set up the chain by calling _.chain and pass in the input data. The input for the functions that I'll be calling will be the output of the last item in the chain. So sortBy will get the raw products array. Once sortBy is executed, whatever the result of that is passed in to the last function. Whatever the result of the last function is, is what value will return. So anytime you're chaining together functions, you can chain as many as you want, whatever works for your operation, but when you want to stop the chain and get the final value, you need to call .value. So here, to get most expensive I begin the chain, I sort by price. Now since the sort is ascending I know that the highest price will be at the bottom of the list. So I can call .last and then call .value in order to get the final value. So the most expensive item on the list is $6.41 and with a quick scan through the numbers you can see that's in fact the case. What about trying to set up an average? Here I have another set of products and I'd like to get the average price. So here's a function that I have. I'll pass in the products, then I'll start the chain off by setting up with the products array. Now since this is an object array, I'll pluck out the price value. From there, I can call reduce. This basically does a summing operation. You've seen this a couple of times during the course. So, it sums up all the values that are found in the objects off of the price property. From there I can get the resulting value and then in order to find the average take the result and divide it by the number of products that I have. Then I'm just returning the result by calling toFixed at 2 decimal places in order to round up to the cents amount. Now, you can get as involved as you need to with your chains. So for this example, I'm supposing that I have data coming in from some sort of service and there's a set pattern to the data, but it's not exactly what I need in order to fulfill the requirements that I have for the application. So let's say for a moment that really, what I need at the end, is a list of the cars that come in from the data, but I only want to look at the cars that are not on sale. I need that list sorted by price, in a descending fashion, but I only want to look at unique car names. So, although Charger shows up twice and Mustang shows up a couple times, I only want to know about one occurrence of that. So completely unique values. So, let's scroll down and take a look at the code. In order to get those results first I'll need to filter, because I want to take out all the items that are on sale. Then I'll sort them by the dollar amount. Then I'll extract all of that data out of the object array into a simple array, then I'll get all those unique values, and then return the final value. So when we take a look at the result, you can see that Mustang, Charger, Eclipse, Camaro, and Sienna. So the most expensive car in the list is the Yellow Mustang for $45,000. The next most expensive car that's not on sale is the Black Charger for $41,700. And then comes the Eclipse, the Camaro, and the Sienna. So again, like I said, you set up the chain by passing in the input. Each item in the chain gets the output of the last function in the chain. So I can filter by saying, first of all, I don't want any of the items where the string sale shows up in the description. Then I can take that string and split it on the dollar sign character and make sure it's sorting by the price that's coming in. Then, since everything's still set up in the object array, I can map everything to a simple array. And here I have to take off the trailing comma that comes after the car name, then I can ask for only the unique values and then return the resulting value. That resulting value is an array of car names and that's what I logout to the result window. So, chaining can be very powerful and also help keep your code concise and expressive. Now, as you're working with chains, one of the things that you can do is tap into that chain and add some functionality to the chain along the way. So here I have an array of author records. Each author has an id, name, state, and an empty twitter handle. Down here, and again supposing that this data is coming from a different source than my data source for the authors up there, I have my list of twitterHandles. So this has an id and the twitter handle. So, the id values will match in the author's data as it is from the twitterHandles data. Now I have a function here and its job is to add in the correct twitter handle from the twitterHandles array up to the authors array. So, when this is called the authors array is passed in and I iterate over each author. Then I filter down to just the twitter handle that I want that matches the author that's showing up in the iteration. Once I have a match then I can set the value.twitter equal to the handle.twitter. Now this function works great against the authors array. So, I can use this to tap into a chain. Here I set up the chain against the authors and then I want to look for all the authors where the state equals Florida. Then I can tap in to add the twitter handle and then return the value. So to find all the authors in the state of Florida with their twitter handle associated to the objects is John Papa and John Sonmez.
Keys, Values, Pairs and Invert
Now, as you've probably noticed the keys function is pretty handy. In fact, I've had to use it a couple of times just leading up to the content in this course, because it makes working with objects so much easier to use and it's so simple too. Calling _.keys and passing in your object returns to you an array of the keys that are found within your object. And if it works for keys, it can work for values just as well. So, calling _.values returns an array of the values that are found within the object. Next in the list of functions that deals with keys and values is pairs. Now, when you call pairs against an object, what that will do is return a multidimensional array with the key and value pairs as an array, within the array. So, my log function here kind of flattens everything out so let's take a look at this in the debugger. So you can see that when pairs is called, what you have available to you is an array with two elements in it. And inside the first element is an array with the key and then the value. The second element is an array with the key and then the value. So, depending on how you need to work with your data, pairs can make it very easy to extract out both the key and value information from your objects. And the last function in this family of functions is invert. So if you have an object and you want to make the values the keys and the keys the values, you can call invert and it'll make that switch for you. So when I Run it you can see that by calling the property of Craig Shoemaker on my object, I get the value of name. This is even more clear when we take a look at it in the debugger. Now, you can see the original object has the key of name and Craig Shoemaker is the value, the key of state and California is the value. Once it's been inverted, now I have an object that has California as the key and state as the value. So, it basically just switches it around, it inverts the data within your object so that the values become keys and the keys become values.
Now, there may be a time when you're working with an object and you need to know how many functions or methods are associated with that object. Well, the functions function or the methods alias will return exactly that information to you. So, the code right here might look a little weird, but this is the way to calculate how many functions there are in Underscore. So when I Run this you can see that there's 106 functions in Underscore and that includes aliases and here's the full list.
Extend, Pick, Omit and Defaults
Now what extend does for you is it takes a source object and copies the keys and values over to the destination object and returns the destination object for you. So here I have an author, the name is Craig Shoemaker, and some contact info. So I've got a url and a twitter handle. When I extend author into contactInfo the extended object is all of the information put together. Now, one of the things that you want to be aware of is that overwrites are possible. So, we take that same example and adjust it just a little bit. In the author object I have a value for url. In contactInfo I have a separate value for url. But when I extend author into contactInfo, the results is that the url value has been overwritten from what is in contactInfo, given priority over what was in author. So make sure if you have any keys that match up in the objects that you're extending, that the data you want to keep shows up in the target. Now pick allows you to manipulate an object so that only the keys that you pass in to the pick function are the keys and values that will end up in the resulting object. So here I have my author object and all I'm saying is that I want the name and url data to come through in the resulting object. So, the picked object ends up looking like this. Since I didn't add the twitter key in the white list of the properties that I wanted, that was removed from the result of the pick function. Now, if pick allows you to work with a white list, omit allows you to work with a black list. So here I'm saying, I'm working with author, calling omit, but I want to omit out the twitter property and the url property. So, all that's left is the name property. So, if you have an object and you mostly want to keep the construction of that object intact, but just remove a little bit of data, omit is the way to go. The defaults function gives you an easy way to provide default values for objects that you're working with. So take a look at the defaults object here. You can see that I have active is true and the bio URL, which is the base URL for Pluralsight authors. Then I have my author object where I've got name, url, and twitter handle. So when I call defaults and pass in defaults and then author, the result that I get is an object that has the default values added to it, along with the original data that was found within that object. Now one of the things that you need to be aware of is that by calling defaults, it will overwrite existing values. So, when you take a look defaults that I have here, notice the URL that I have for bio, again the base URL for author bios. If you take a look at the author object down here, I have my full url for my Pluralsight author bio. Now, when I call defaults, you can see that the two properties and values that didn't exist in the author object that were in defaults, active and reviewer, show up in the resulting object, but bio is the data that's from defaults. So, all this to say that if you have an object that already has a value in it, you may not want to call defaults on it unless you're okay with the default value being copied over to that object.
The clone function gives you an opportunity to create a shallow copy of an existing object. So here I have my minion object and the name is Storm Trooper and I'll clone him in order to make a Clone Trooper. Now, you can see from the result that the Clone has the same schema as the original. Now one of the things you might want to be aware of is the fact that if you try to do a deep clone it may not work exactly the way you might think. In fact, it doesn't do a deep clone at all. It only does a shallow copy of your object. So let's take a look at this data here. I've got a minion, again named Storm Trooper. His duties are to Clean AT AT and Find Leia. Now, the clone is going to get the same thing. Except what I'll do at this point is change his name from Storm Trooper to Clone Trooper. Let me Run it and see what we've got. Alright, we've got the original named Storm Trooper, duties just as defined. And we have the clone named Clone Trooper, duties just as defined. So, let's come down here a little bit and see what else is going on. Then I go into the minion's duties and I push in a new value into that array, 'Fix Vader\'s Tie Fighter'. So here's the label for updating the minion's array and now let's take a look at the resulting data. The original, named Storm Trooper, has all the duties that he used to have, plus the new one of Fix Vader's Tie Fighter again, but so does the clone. So, all this to say that if you try to clone a deeply-nested object, any of the deep members of that object are still kept in reference. So, this is a handy function when you need it, but just be aware of exactly how it behaves.
In this module, I'll discuss the final set of functions found in the Utility section of Underscore. Now make sure to stick around for the last module in the course while I'll take you through an integration demo where you can see many of the functions you've become familiar with up to this point working together in a more real-world implementation. For now though, we'll discuss the series of utilities that round off the effectiveness and ability of Underscore.js.
The noConflict function comes into play when you're working with other libraries that may be using the underscore character in order to establish their API or if you want to use the underscore character for some other reason within your application. Now, noConflict gives control of the underscore variable back to its previous owner and then the return value of noConflict is a reference to underscore. So here within the demo you can see I'm calling _.noConflict and sending the u variable equal to that. So now, after calling that command, u is the Underscore library instead of the underscore character. So now, when I execute the each function I get the results in the results window using the u character instead of the underscore character. Now, I'm using Underscore under the hood for my code viewer so I have to do things a little bit manually within my iterator. But, in the end you use noConflict when you want to make sure that Underscore won't have a conflict with any other libraries.
Now to be thorough, I have to include a demo here for identity. This has to be one of my favorite functions in Underscore and it has to do with what you find in the docs. So, right here it's got Straight from the docs. This function looks useless, but it's used throughout Underscore as a default iterator. Now, let me explain what that means. Well, first let me explain what it does. What identity does is it takes in the value and it returns the value. So, in my code here when you see does author equal _.identity(author) and that returns true. That's because the identity function is simply turning around and passing author back up. Now why in the world would the library need something like this? Well, like the documentation says, it needs a default iterator. So, if you're doing something like map or each or you're running one of these functions that has an iterator in it, but you don't provide an iterator, it needs something to be able to run at that point. And so, it will use identity in order to just return what it was before. That way, it doesn't affect any of your data. So, identity seems a little silly, but when you understand the purpose and why the developers have it in there, you can see that perhaps as you build your own functions, that maybe you mix into Underscore and I'll show you that in a few minutes of how you can do that. But maybe, as you build your own functions to work with these sets and build up some functional resources you might find some use for it too.
Times and Random
Using mixin allows you to extend Underscore with your own functions and your own implementations. Here I've created a mixin for trimEnd. So, I pass in an originalString, pass in the string to trim, and then just do a substring operation against it in order to return the final result. Now notice the way you want to set this up is you call the mixin function and then you pass in an object with a key of whatever you want that function name to be and then the value of the actual function itself. Now when you go to call it, you can use either the object-oriented approach or you can use the more functional approach. Either way, you get the same result. So, what about another implementation? What about Reverse String? Here's the same thing, I'm calling mixin, I've got my hash or my key value of reverseString. The function here implements what I want it to do and then I can call it either of the two ways that is comfortable. So these are fun, but there are some other things that you might want to consider doing maybe sum, average. Here, I created an implementation for sum. Called mixin, passed in sum, created a function for it. Here I can call Underscore's reduce in order to reduce my array down to a single value. Now I'll look at the number that comes in to see if it's an actual number or if it's an object. If it's an number I'll just use that value. If it's an object, then I need to take a look at the key argument to find out which property in that object has the value I'm looking for. Then I can add it up to my memo and return that final value and that allows me to sum up numbers. So here I have my object-oriented approach where I can pass in an array and call sum or I can use my functional approach against an array of objects and I just pass in the key as the second argument. So, there's a lot to be gained here by giving you an opportunity to mix in your own implementation with the Underscore library.
Now, when you're generating elements for the UI, often the DOM elements that are created need to have a unique ID. Now, you can handle that manually or you can use Underscore's uniqueId function in order to help you generate those values. So, you can either pass in a prefix or you can just call the function by itself. Here, just by calling the function I get 9, 10, 11 or I have the prefix of 12, 13, and on down the line. When I Run it again you can see that it's keeping a running count of the values that are used on this page. So that you generally know that you get a unique ID every time you call the function.
Escape and Unescape
Now often times you want to escape a string in order to make sure that you're either dealing with non-executing code or protecting against malicious attacks or you just don't want something that someone types into an input field in your web page to affect the markup in any way. So, by calling escape, I'll Run this and you'll see that you need to take a look at the console to see the results. You get the same lines of text as from above, the good, the bad, and the ugly with the HTML entity for the ampersand and then the escape characters for the HTML elements, as well as the markup for this HTML pre-element. And if you can escape a string of course you need to unescape that string. Now, I'm using the escape function within my code viewer here so you're actually not seeing the real code. So, let's open up the markup and you can take a look and see what's really here. So, this is the code that should be printed up in the code viewer and now when I Run, we can go over to the console. We get the unescape strings, which bring them back to their raw format.
The result function gives you some flexibility when you're working with objects. Sometimes you'll have members of an object that are property and sometimes they'll be members that are functions. And what result does is whether or not it's a property or a function if you call result on that member for your object, it will either return the value of the property or execute the function and return its value. So you can see when I call result on author and pass in first, it's going to the first property and returning Craig. But, when I call result on author and pass in getFullName it executes the function of getFullName and returns to me the concatenated string of my first and last name. So, as you're passing around functions and objects around your scripts, you may not know exactly what the object is that you have and when used with other Underscore functions like keys and functions and getting all the metadata about objects that you're working with, you could very easily find yourself in a situation where you need to use result to get the value of something out of an object. Because you won't know necessarily until run time exactly what you're trying to execute.
Template: Part 1
Template: Part 2
Template: Part 3
With the grab-bag of utilities in Underscore you can easily extend the Underscore library, control markup on your page, and even handle client-side templating. Now join me in the next module, where I take you on a guided tour of using Underscore by example in a more real-world context, where together we'll build a sales summary report for a real estate company.
Underscore by Example
Now that you've had a chance to see Underscore functions working in sort of an atomic fashion, kind of seeing what the arguments do, seeing what the returns values are. I want to show you now what it looks like to use it in something that's perhaps, maybe a little more real world. Now, this is still a mock company with mock data and all that kind of stuff. But the idea is, is that this page is set up to be constructed in a way that you might do it in an everyday scenario. So, welcome to CraigHomes. This is a sales report done at the end of the year, say for the Management Team. Now the first thing they want to know is the sales by month in millions. Now, you'll see that the data that's being fed into this page are individual records of sales. And so I'm using Underscore to create aggregations in order to make this table happen. On top of that I'm doing some comparison against plan. So, for this report there's a business rule that says, anytime the sales were over 7 million dollars in a single month, they're above plan. Anytime they're under 7 million dollars, they're below plan. So the items colored in that light-gray background show that they were above plan for that month. Then there's the Quick Facts. The highest sale of the year was $672,542, lowest $219,442, and on average houses sold were around $465,406. You can see a list of all the properties that were sold, all 200 of them, which you can see down here. And you can even go in here and filter the properties. Let's say we want to look in a specific county, like Riverside County, and find everything that shows up in Temecula. So I can filter based off that. You see now there are two properties down there and we get those two properties that show up. And I think I have like, I don't know, five to seven sample images and wouldn't you know it, within my filter the two records that show up have the same picture. Well, we can clear that filter and you can see that now we're back to all 200 of those properties. As we scroll a little bit further down, you can see we're able to send that information to Marketing and also send to the Manager. So, the interaction and the data within this page is fairly simplistic, but I'd like to show you how I built it using Underscore.
mockJSON is a jQuery library that makes it easy to generate random data for testing and prototype purposes. It's perfect for this application because when you download these files, you'll get the feeling that you're working with data that comes from a service or perhaps a database, but it's not required to set all that up in order to make this page work. What's nice about mockJSON is that you can define templates to shape your data and even create custom keywords, which give you an opportunity to provide seed data that's then used randomly per your direction. So, when we take a look at the template here first I'm saying I want to create a list of 200 properties. Now the way mockJSON works is you can provide a range to show up right here after the pipe character. If I were to pass in something other, say 0-200, then it would create a random number of properties between 0 and 200, but I want exactly 200 properties so I'm passing in 200-200. Then I'm saying I want the id property, increment by 1 each time and I'm giving it a default value. Then within the template I'm using some keywords. So, image is a custom keyword and I'll show you how I implemented that in a moment. For streetAddress, I wanted to make sure that the street addresses looked kind of realistic so I didn't want any 0s to show up in there, so I have a keyword that will inject the first number. And then there's Native Keywords found within mockJSON and so I'm using number three times here. Then I have another custom keyword of STREET_NAME where I've provided a list of street names that I can randomly choose from in order to place into streetAddress. Same goes for CITY_NAME, and then the zip code, I'm seeding it with 90 because most zip codes in California, at least Southern California, start that way and then three numbers. COUNTY also is a custom keyword, which I defined. And then, I'm generating some random data here for bedrooms, bathrooms, saleMonth, saleDay, squarefeet, and even price. Here I'm giving the ranges after the pipe character. So, bedrooms can be any value between 2 and 6, bathrooms 2 and 4, and on and on. I'm just providing some default values here, but those'll be overwritten when the random data is generated. Then finally, I'm using some built-in keywords for mockJSON where you can generate an uppercase letter and some LOREM_IPSUM text. Sometimes it's long, sometimes it's short, it just depends on how the randomization comes out. So this template, when it's used from generateFromTemplate, creates an array of 200 properties based off the constraints that I've given it and so you can see, on the running page, how this shows up within the list.
Now mockJSON comes with a really useful list of keywords. So you can get numbers, you can get dates, you can get email addresses and names and things of that nature. But sometimes you need something that's a little more specific just to flush out the data that you'll be using within your application. So here, I'm creating some custom keywords so that as random data is generated, mockJSON will randomly choose from items within the arrays based off of these keywords. So, for STREET_NAME when it generates a record, it could choose anything between Wallaby Lane, Sixth Street, and even Crazy Way. So, this just maintains a list of street names that it can use when it generates those objects, and the same thing for CITY_NAME. So, here are some cities around Southern California and also some counties. Now, obviously this is all random data so there's times when a city name will appear associated with a county that doesn't make sense in real life, but that's okay, this is just random data. So I created one for CITY_NAME and also for COUNTY. And I also created one for FIRST_NUMBER and for IMAGE. Again, the reason I wanted to do this with FIRST_NUMBER was because I didn't want 0s showing up in the first character of a street address, a little nitpicky I know. And then for IMAGE I have some sample images and I just wanted an easy way to be able to pull out a URL for those images. There's obviously a number of different ways that you can handle that, but this was just one way that I chose to do it. So, this module here creates all the customizations for mockJSON that's required and in fact, I don't know if you noticed up at the top, but I'm creating the mockJSONData variable and setting that equal to a self-executing module. So, as soon as I include this into the page, this script will run and it will customize mockJSON for me, as I need.
The next module that I'm bringing in are my mixins. Now, the first mixin that I'm defining is numberWithCommas. So I have a regular expression that will take a number and create common groups every third digit. This is really useful for rendering out currency. Also, just as I showed you during the mixin demo, I went ahead and included the sum function here. This is really handy because I'll be chaining some functions together and adding sum to the mix really helps out a lot. Now like I did with numberWithCommas, the whole idea behind Underscore, chaining, and functional programming used with strings can be really useful. Underscore.string is a project that includes dozens of different functions that are really useful to use with strings. They have a number format one, they have capitalize, chop, clean, trim, on and on and on. If you want to find out more about Underscore.string, check it out at this URL here.
Data Module: Overview
The data module is where a lot of the work is going on so I'll give you an overview and then I'll dive into some of the specific parts of it. So first, I just have an array that has all the month names. Now, the month names don't appear as they are here in this array. I'll manipulate those names a little bit before I display it out on the screen. Here planTarget defines the value that Management wanted to hit, which decides whether or not sales were on plan or not. So, I'll use this to evaluate whether or not to add a CSS class to elements when I'm rendering out the summary table. Then I have a sub module, which will create the abbreviated months, a sub module, which handles the properties, and then an object that handles aggregations. This module uses the revealing module pattern so what's exposed from the module is the aggregations, months, planTarget, monthsAbbrev, and the properties list.
Data Module: Months
First let's talk about the abbreviated months. The way I've set this up is to be a sub module so that when you go to data.monthsAbbrev.get and call that function, you'll get the data that's created by this function. But at the same time, I made it a sub function so it's cached. It's a little overkill for what I'm doing right now, but at least this way if you call monthsAbbrev more than once, it doesn't have to recalculate, it'll just grab the cached version. So the way this module works is that the months that are available up here in this array are passed into the module down here. Now they're made available within the function arguments at this point and then I create a get function. If the results are null I'll generate the value that goes in the results. So what I'll do is map the months so that I'm getting just the first three characters of each month. So here, map is giving me a chance to transform the data that's coming in as the months array and just provide the abbreviated versions of it. Now of course, this is a demo application so I just could have created the array of months with three characters at a time, but this is an idea that gives you a chance to see of why you would want to use map. Often times, data comes into your application and you don't always have an opportunity to change things on the server end or maybe it's just easier to do it within the scripts. So this shows you one way in which you can do that. So once I have the results, that's returned back up to the caller. When the function data.monthsAbbrev.get is called, it'll return an array of the abbreviated months' names.
Data Module: Properties
Next, I'll discuss the sub module for properties. Now, much like I did with the months, I'm creating a cached version of the properties, just so that when a call to getProperties is done more than once the information's not being recalculated. And particularly in this case, that's important because I'm dealing with randomly-generated data. So, if I need to get that information more than once I need to make sure I'm working with the same data that was generated in the beginning. So, I'll scroll down to the get function. And you got a preview of this when I was discussing mockJSON earlier in this module. Here the get function first looks to see if the data variable has anything in it. If it doesn't, then it generates from the template using mockJSON the list of 200 properties. Then once that's done, it returns the properties that are generated from mockJSON. Now notice the array as it's generated from mockJSON is found at data.properties because the template was declared with properties as the name of the array here. So down at the bottom, to return the array, I need to return data.properties. So now that we can get the properties, it can be used a number of different ways. Let's go back up to the top of the module and you can see that there's a function here for filterBy. filterBy accepts a filter condition and then I can use Underscore's where function in order to filter the properties based off of the condition that's passed in. Now remember, where will take an object literal. So you can search the data that's passed into the where function based off a number of different conditions that you pass in to the function. So, I could be looking for something in a certain county and also in a certain city. And this will handle that nicely. So, once the data is filtered I simply return that value back up to filterBy. Now the data that's generated for the properties each has a county associated with it. So in order to get the list of counties within my data, I can call Underscore's groupBy passing in all the properties and telling it I want to group by county. Now remember, the way that Underscore does grouping is that it'll return to you an object where the keys are the criteria that you used in order to do the grouping. So my object will have a key for Riverside or Orange or San Bernardino. So, in order to show just those counties up on the page I can use the keys function to extract out those values, sort them, and then return them up to the caller. And the same approach is used for getCities. I can group the data source by cities, extract out those keys, sort them, and then return it up. So the properties module, which is actually a sub module of data, also uses the revealing module pattern. So the only thing that the caller will be able see is get, which gets all properties, getCounties, getCities, and the filterBy function.
Data Module: Aggregations
The last section of the data module are the aggregations. Underscore really helps in making aggregations easy when you're working with raw data. Starting down here, I'm passing in the whole properties collection to my aggregation object by calling properties.get and passing it in to aggregations here. Once I have those available, then I can begin to calculate the highest price. Using Underscore's chain syntax makes this really easy. So I can take a look at the properties, pluck out the price, find the max value, and return that value. If I want to find the lowest price, basically the same thing except here, I'm chaining on properties, plucking out price, finding the minimum value, and returning that. If I want to find the average price, I can chain on properties, again pluck out price, call the sum function, which if you remember is a mixin that I created in the mixins module, and then grab that ultimate value. Once I have the sum of all the prices then I can divide it by the size of my properties collection, which gives me the average and then I'll just round the final result so it looks nice when I show it on the page. Looking at totalSalesByMonth is pretty easy actually. If I take the properties and group them by sale month, and then go through that collection using the each function and sum up the price found in that group, then I'm very quickly able to build up an object that has the total sales by month. And here I'm just reusing the same object that was created by groupBy and just injecting the sum back into the values of those keys. Once I have those totals, I can return them back up to the caller. Now for monthlySalesSummary, I just take the total sales by month and do a little mapping here. Now the sale prices for these homes are in the hundreds of thousands of dollars. But if you remember from the summary table, I'm just showing the summaries in just a few digits. So, in order to make this happen I'm mapping the total sales and doing some calculations on them, so that I get the type of summary value that I want. Then I can return up those monthly summaries, which the view model will use to inject into the table. So with the months, formatted months, the properties, and aggregations that rounds out all the functionality that's required for the data module.
ViewModel: Summary Table
ViewModel: Render Properties
The next order of business is to render the properties out on the page. So, I'll right-click here and I'll go to definition. So this function accepts either the collection of properties that I'm working with, and this argument comes into play when the data's been filtered and I want to render these properties out on the page, but it's not all the properties. It's just the filtered set of them. So here I have an opportunity to use isUndefined for my properties argument. So if nothing's passed in to this function, I'll go to my data module and get all properties. Then I can render the template by passing in the properties and the propertiesRenderer. Now the propertiesRenderer is the compile template for my properties. So, let's take a look at the markup for that template. Starting up here I have my script tag for "properties-list-template" and again remember, I'm setting the type to "text/template" so the browser skips all this, but I can still work with it like it's regular HTML. And also if you remember from the beginning, I'm using Bootstrap to handle some of the layout concerns so I'm using the layout grid here in order to set up a fluid row. The first cell in my row with span 2 cell lengths and offset it by 1. Then I create an image, an object that will be coming into this template will have an imageURL property on it. And if you recall, that's the value that will come from that custom keyword that I created from mockJSON. Then the streeAddress, city, state, and zip. The county and as I scroll down here, the number of bedrooms, bathrooms, squarefeet, and description. So that will be generated for each one of the properties that's fed into the template. And the final markup will be injected in here to this div with the id of "properties-list". So once I have all the markup generated for my list of properties, then I can inject it in to the propertiesList div and this variable here points to the div element that I showed you underneath the template. And then when I'm going back from filtering and clearing the filter, sometimes the scroll position isn't the right place. So, I'm using jQuery to scroll to the top of that div. And then I also have an element that displays the number of properties that's in the current collection. So here I can use the Underscore size function to report out the number of properties that happen to be in the current list.
ViewModel: City, County and Quick Facts
Now to finish off rendering what's left of the UI, the function will render out the counties, cities, and the quickFacts. So, in order to the counties I just need to go to my data module and from the properties get the counties. Once I have those I can use Underscore to iterate over each one and append an option in to the selection list that I set aside as countyList. And I can do the very same thing for the cityList. Finally, for the quickFacts, I have an object here that has a property for the highest price, the lowest price, and the average. Here I'm using my mixin to format my number to show the numberWithCommas for each one of those values. Once the quickFacts objects is fully prepared, then I can use my renderer to render it against the quickFacts template, which I compiled under the quickFactsRenderer. Let's take a look at that really quick. The template for the quickFacts is right here. Again, a script tag with a type of "text/template" and then I have some really simple markup here. It's accepting high, low, and average, which is exactly what the object has that I'm passing in to the renderer. The markup from that template will be injected into this container here for the "quick-facts-container". Again, the comment shows you exactly where it'll be placed. So once the markup is generated for the quickFacts then I can inject it in to the "quick-facts-container" and then at that point all the information that needs to be rendered, is rendered onto the page.
ViewModel: Process Elements
The next thing the init function does is runs the processElements function. And the whole purpose of processElements is basically just to attach things to the DOM and get elements set up for interaction and styling. So here, I'm selecting all the tables in the markup, adding the Bootstrap 'table' class, as well as the Bootstrap 'table-bordered' class. Then if you remember from my properties list I have some links here for Map and More Info. They don't do anything so, in case you click on it, I just wanted to show this message here for Sorry, not implemented. So using the on function allows me to do that, even when the properties are filtered and reset back to the original list. Then I add click handlers for each of my buttons, filter, click, send-to-marketing, and send-to-manager. And I'll go over each of those functions in just a moment. Then lastly, I have some elements on my page, which I've marked with the defer CSS class. Originally, those items are hidden in the browser using display none and then I use jQuery to fade them in. It just gives it a nice look. This way the page shows the logo and some of the design elements first, but then as all that processing and all of the gluing together of the UI elements is being done, it will finally fade in once everything is ready and available to show the user.
ViewModel: Filter and Clear Filter
Now filtering the list gives me a chance to use the Underscore's where function in order to filter out only the properties that I want based off of the criteria that comes in from the user. Now, that's all wrapped up by the filterBy function, but what I need to do is make sure that I'm passing in an object that represents the filter that I want. So here within the filterList function, I'm declaring a few variables here for properties and filter. So first, I'm looking at the countyList and if the 'selectedIndex' does not equal 0, then I can extract the value out of that list and create a property on my filter object for county. Then I can also take a look at the cityList. Again, if there's a selection there grab that value and create a property on my filter for city. Now I'm looking to see if filter does not equal an empty object. So basically, do I have some data that's coming from the user to filter on? If that evaluates to false, then I can pass my filter object down in to filterBy and the return value will be the filtered list of properties. Once I have those properties, I can pass them in to renderProperties and the UI is updated with only the filtered list of properties. With clearFilter I'm calling renderProperties with no arguments. So, if you recall that will go and grab all of the properties and render them out onto the page. And then I'm just resetting my select list here by setting the 'selectedIndex' to 0 on my county and cityList.
ViewModel: Send to Buttons
The last bit of logic to implement in the sale summary is the implementation for the Send To Marketing and Send To Manager buttons. Now Marketing wants to know about the properties that have been sold this year from this report, but it doesn't need all the data that's found within this report. So here, I'm getting all the properties and then running a map on it so that the final result of this operation will be a new array of objects, but before I do that, I want to omit out a number of the elements from those objects. So, they don't need the description, squarefeet, imageURL certainly. They don't need the county, bedrooms or bathrooms. But everything else within that property object, they want that information. So I can call Underscore's omit in order to trim down the data that's found within that object. Now, I'm not actually sending it anywhere here, but you can see how I'm using omit and map together in order to create a filtered list of the data to send it off to some other destination. sendToManager does a similar thing. Here I'm calling map against all the properties, except the Managers only need to know about the property ID and the associated price. So here, instead of omitting everything, I'm calling pick just to grab the ones that I want. And again, once I have that result I can send it off somewhere, an AJAX service, web service, whatever's necessary for your application.
Craig Shoemaker is a developer, instructor, writer, podcaster, and technical evangelist of all things awesome.
Released8 May 2013