This is a Servoy Tutorial on how to use some of the more advanced Javascript array functions. In previous Servoy Tutorials, we talked a lot about objects and object-oriented programming , but I wanted to make sure you understood the full power of the array and its functions as well. Without a deep understanding of how some of the more advanced array functions work, your not properly equipped for your journey to Servoy Nirvana. Solving coding challenges with efficiency and brilliance is the destination, and without a full understanding of array functions, you can not achieve it. Some of the things covered here you may already be familiar with, and some of them, I bet, will humble you.
A Javascript array is an object, and can be created two ways, either using the constructor, or as a literal. The literal is the preferred way and the approach that will be used throughout this Servoy tutorial.
1 2 3 4 5 | // using the constructor var aArr1 = new Array(); // preferred method; literal var aArr2 = []; |
If you select the Array node under JSLib in your Servoy Developer Solution Explorer view, you will see a lot of methods that you can use with an array, along with two properties, index and length. You probably know how to use index and length, so I am not going to bore you by teaching basic Javascript here. In this Servoy tutorial, I am going to focus on some key methods of the array prototype and demonstrate some of their uses. Hopefully, you will learn a trick or two.
.concat(), .join()
The array .concat() method is normally used to join two arrays together, like this:
1 2 3 4 | var aArr1 = ["a", "b", "c"]; var aArr2 = ["d", "e", "f"]; var aArr3 = aArr2.concat(aArr1); application.output(aArr5); // [a, b, c, d, e, f] |
So, what if you want to make a copy of an array? You can’t do this by assigning one array equal to another array, because that makes them the same array, meaning what affects the one array also affects the other (they are the same object).
1 2 3 4 5 | var aArr1 = ["a", "b", "c"]; var aArr2 = aArr8; application.output(aArr1 === aArr2); // true aArr1[0] = "ff"; // changing the value in aArr1, also changes the value in aArr2 application.output(aArr2[0]); // ff |
Similarly, writing a function that loops through the first array and pushes onto a new array, works, but is totally lame.
1 2 3 4 5 6 7 8 9 10 11 12 | var aArr1 = ["a", "b", "c"], aArr2 = [], iLen = 0, i = 0; iLen = aArr1.length; for (i = 0; i < iLen; i++) { aArr2.push(aArr1[i]); } application.output(aArr2 === aArr1); // false aArr1[0] = "ff"; application.output(aArr2[0]); // a |
The most efficient way to do copy one array into a new array is to simply use .concat() without passing it any parameters. This makes a shallow copy of the original array, returning a new one. A shallow copy means that the new array still has a reference to the elements, but is not the same object as the original.
1 2 3 4 5 | var aArr1 = ["a", "b", "c"]; var aArr2 = aArr1.concat(); // magic; the array is shallow copied application.output(aArr1 === aArr2); // false aArr1[0] = "ff"; // changing the value in aArr1 has no affect on aArr2 application.output(aArr2[0]); // a |
I will just mention that array .concat() is often confused with .join(). The difference is that .join(separator) takes an array with elements and combines them into a string, separated using the provided separator, or defaulting to “,” as the separator. It cannot be used to join two arrays together, or make a shallow copy of an array.
1 2 3 | var aArr = ["Gary", "Dotzlaw"] application.output(aArr.join("|")); // Gary|Dotzlaw |
.filter()
I want to talk about a few things before we actually get into how .filter() works, and how you should use it. Arrays can store pretty much anything as elements;, strings, numbers, JSRecord, JSFoundSet, JSDataSet, objects, and even other arrays.
You can store simple information in an array, like strings (bet you knew this!):
1 2 | var aArr1 = ["a", "b", "c"]; application.output(aArr1); // [a, b, c] |
You can store more complex data in an array, like additional arrays (you probably knew this):
1 2 3 4 5 6 7 8 | var aArr2 = [ ["Gary", "Dotzlaw", "Developer"], ["Some", "Buddy", "Employee"], ["Any", "Buddy", "Developer"], ["Another", "Buddy", "Employee"], ["No", "Buddy", "Manager"] ]; application.output(aArr2[0]); // ["Gary","Dotzlaw","Developer"] |
You can store complex information in an array using a key, in what is know as as an associated array (you might have know this):
1 2 3 4 5 6 7 | var aArr3 = []; aArr3["Gary Dotzlaw"] = ["Gary", "Dotzlaw", "Developer"]; aArr3["Some Buddy"] = ["Any", "Buddy", "Developer"]; aArr3["Any Buddy"] = ["Gary", "Dotzlaw", "Developer"]; aArr3["Another Buddy"] = ["Another", "Buddy", "Employee"]; aArr3["No Buddy"] = ["No", "Buddy", "Manager"]; application.output(aArr3["Gary Dotzlaw"]); // ["Gary","Dotzlaw","Developer"] |
Associated arrays are kind of cool, because you can check the array, by key, to see if the data is already there. For example, perhaps you are looping through data and you need to store information about each unique occurrence of a particular data type. You can build an associated array doing something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var aArr3 = []; aArr3["EST"] = ["E0001", "E0002", "E0003"]; aArr3["ORD"] = ["O0001", "O0002", "O0003"]; aArr3["JOB"] = ["J0001", "J0002", "J0003"]; // in a loop through data you encounter foundset record (rRec.type = "JOB", rRec.doc_num = "J0004") // just using an object to simulate the record data var rRec = { type: "JOB", doc_num: "J0004" } if (!aArr3["'" + rRec.type + "'"]) { aArr3["'" + rRec.type + "'"] = []; // if the associated array does not exist, create it }else{ if (aArr3["'" + rRec.type + "'"].lastIndexOf(rRec.doc_num) === -1){ aArr3["'" + rRec.type + "'"].push(rRec.doc_num); //if the element is not in the associated array, push it } } application.output(aArr3["'" + rRec.type + "'"]); // [J0001, J0002, J0003, J0004] |
One of the problems in using an associated array, is that if you wanted to store more information for each of the keys, like rRec.total_amount, rRec.status, etc., you would have to store an array of information as each element, and remember the mapping of the data. It would look something like this:
1 2 3 4 5 6 7 | aArr3["EST"] = [ ["E0001", 1212.13, "Open"], ["E0002", 12456.99, "Won"], ["E0003", 142.00, "Open"] ]; application.output(aArr3["EST"]) // [[E0001, 1212.13, Open], [E0002, 12456.99, Won], [E0003, 142.0, Open]] |
Okay, now that we have that all out of the way, we can learn about how to do things more elegantly, using the array .filter() method. To demonstrate the power of the filter we are going to use objects as the elements in our array. Suppose we wanted to extract all the objects whose “role” property is set to “Manager”, and put them in a new array. A common way of doing that would be to loop through the objects in the array and read the role property, then push the object onto our new array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // an array with objects as elements var aArr = [ {first: "Gary", last: "Dotzlaw", role: "Developer"}, {first: "Some", last: "Buddy", role: "Employee"}, {first: "Any", last: "Buddy", role: "Developer"}, {first: "Another", last: "Buddy", role: "Employee"}, {first: "No", last: "Buddy", role: "Manager"} ], aMngr = [], iLen = aArr.length; for (var i = 0; i < iLen; i++) { if (aArr[i].role === "Manager"){ aMngr.push(aArr[i]); } } application.output(aMngr); // [{first:No,last:Buddy,role:Manager}] |
A more elegant and efficient way to do this is to use the array .filter() method, which accepts a callback function that returns true or false. In that callback function we examine each element in the array individually. Here is an example of how to extract the objects with “role === ‘Developer’”.
1 2 3 4 5 | var aDev = aArr.filter(function(element){ return (element.role === "Developer"); }); application.output(aDev); // [{first:Gary,last:Dotzlaw,role:Developer}, {first:Any,last:Buddy,role:Developer}] |
In addition to the callback function, the array .filter() method provides the callback function access to the index and original array, like this:
1 2 3 | var aDev = aArr.filter(function(element, index, array){ // filtering }); |
Finally, just because you need to know this, if you truly want to write awesome code, you can chain functions together, and .filter() the objects, extract specific data from the object using the .map() function, and then use the .sort() function to sort the result, achieving coding excellence in one awesome line of code. We will learn more about .map() and .sort() in a bit, so stick with it.
1 2 3 4 5 6 7 | var aDev = aArr.filter(function(element){ return (element.role === "Developer"); }).map(function(element){ return element.first; }).sort(); application.output(aDev); // [Any, Gary] |
Ah…, .filter(), one of my favorites. Put this one in your toolbox; its essential for your journey to greatness.
.every(), .some()
Using .some() allows you to loop through the elements in the array using a callback function, until it finds one where the callback returns true. As soon as the callback function finds the first element that satisfies the condition, it exists. The callback is passed the element, the index and the original array. An optional second argument can be passed giving you the context “this” within the callback. The .every() method works the same way, but with the every() method, callback returns on false, rather than true.
1 2 3 4 5 6 7 | var aArr = [1,23,8,4,21,10], nNum = 0; aArr.some(function(element, index, array){ if(element > 10 && element < 22) nNum = element; return element > 10 && element < 22; }); application.output(nNum); // 21 |
.forEach()
The .forEach() iteration method allows you to loop through each of the elements in the array using a callback which is passed the element, the index and the original array. An optional second argument can be passed giving you the context “this” within the callback.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var aArr = [1,23,8,4,21,10], nSum = 0; aArr.forEach(function(element,index,array){ nSum += element; }); application.output(nSum); // 67 // using this var aArr = [25,25,25], aArr2 = [100]; aArr.forEach(function(element,index,array){ this[0] += element; }, aArr2); application.output(aArr2); // [175] |
.indexOf(), lastIndexOf()
The .indexOf() method looks up array element positions and returns -1 if not found. A second argument can provide the start index for the search start. If you want to search the other way, use .lastIndexOf(). You probably know by now how to use these two, but did you know that you could do the following?
1 2 3 4 5 6 | // Instead of if statements with multiple "or" pattern var sString = "Bill" if (sString === "Gary" || sString === "Bill" || sString === "Frank" || sString === "John")application.output("true"); // Do this if(["Gary", "Bill", "Frank", "John"].lastIndexOf(sString) !== -1) application.output("true"); |
.map()
The .map() method is a powerful iteration method and can be useful in several scenarios. The callback function is called once for each element in the array which constructs a new array from the callback.
1 2 3 4 5 6 | // type casting var aArr = [1, "45", "23", 7] aArr.map(function(element, index, array){ parseInt(element); }); application.output(aArr); // [1.0, 45, 23, 7.0] |
I have found .map() useful for building objects from arrays of data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var aArr = [ ["Gary","Dotzlaw","Developer"], ["Some","Buddy","Employee"], ["Any","Buddy","Developer"], ["Another","Buddy","Employee"], ["No","Buddy","Manager"] ], aArr2 = []; aArr2=(aArr.map(function(element, index, array){ return { firstName : array[index][0], lastName : array[index][1], role : array[index][2] } })); application.output(aArr2[4]); // {firstName:No,lastName:Buddy,role:Manager} |
Also, as we saw earlier in the .filter() example, we can use .map() to access properties inside objects that are stored as elements in an array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var aArr = [ {first: "Gary", last: "Dotzlaw", role: "Developer"}, {first: "Some", last: "Buddy", role: "Employee"}, {first: "Any", last: "Buddy", role: "Developer"}, {first: "Another", last: "Buddy", role: "Employee"}, {first: "No", last: "Buddy", role: "Manager"} ], aDev = aArr.filter(function(element){ return (element.role === "Developer"); }).map(function(element){ return element.first; }).sort(); application.output(aDev); // [Any, Gary] |
.pop(), .push(), .shift(), .unshift()
You should already know how to use these four methods (if not, go learn them, they are basic requirements). I don’t have much to say about these but I wanted to make certain you know that you can push more than one element at a time.
1 2 3 4 5 6 7 8 9 10 | // Push more than one string at a time var aArr = []; aArr.push("Gary", "Dotzlaw", "Developer"); // Push multiple objects at one time var oObj1 = {first: "Gary", last: "Dotzlaw", role: "Developer"}; var oObj2 = {first: "Some", last: "Buddy", role: "Employee"}; var oObj3 = {first: "Any", last: "Buddy", role: "Developer"}; var oObj4 = {first: "Another", last: "Buddy", role: "Employee"}; aArr.push(oObj1, oObj2, oObj3, oObj4); |
.reverse()
I will leave this one to your imagination.
.slice()
I’m sure most of you have used the .slice() method before, but did you know it can be used for two purposes? The most common use is to remove a small subset of an array and return a new array.
1 2 3 4 5 6 7 | var aArr = [1,2,3,4,5,6,7,8,9,0]; // Start position only application.output(aArr.slice(2)); //[3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0] // Start and end position application.output(aArr.slice(2,5)); //[3.0, 4.0, 5.0] |
Did you know, however, that like .concat(), the .slice() method without any arguments can be used to shallow copy an array?
1 2 3 4 5 | var aArr1 = ["a", "b", "c"]; var aArr2 = aArr1.slice(); // magic; the array is shallow copied application.output(aArr1 === aArr2); // false aArr1[0] = "ff"; // changing the value in aArr1 has no affect on aArr2 application.output(aArr2[0]); // a |
.sort()
Okay, I bet we all think we know how to use the array .sort() method, but maybe I should just review them anyways?
Here is how to sort a numeric array in ascending order.
1 2 3 | var aArr1 =[134, 88, 2, 99]; aArr1.sort(function(a,b){return a - b}); application.output(aArr1); // [2.0, 88.0, 99.0, 134.0] |
Here is how to sort a numeric array in descending order.
1 2 3 | var aArr2 =[134, 88, 2, 99]; aArr2.sort(function(a,b){return b - a}); application.output(aArr2); // [134.0, 99.0, 88.0, 2.0] |
Here is how to randomize the array elements, which is quite often necessary for scheduling apps and other situations.
1 2 3 | var aArr3 =[1,2,3,4,5,6,7,8,9,0]; aArr3.sort(function() {return 0.5 - Math.random()}); application.output(aArr3); // [7.0, 0.0, 1.0, 8.0, 2.0, 5.0, 3.0, 9.0, 6.0, 4.0] |
Here is how to sort an array of objects, both ascending and descending, using a specific key (in this case “first”). If you are going to work with objects, you need to know these two methods. Clip the snipets and add them to your utils library.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | var aArr = [ {first: "Gary", last: "Dotzlaw", role: "Developer"}, {first: "Some", last: "Buddy", role: "Employee"}, {first: "Any", last: "Buddy", role: "Developer"}, {first: "Another", last: "Buddy", role: "Employee"}, {first: "No", last: "Buddy", role: "Manager"} ]; // Sort the objects in the array by a property (ascending) var aArr2 = aArr.sort(function(a,b){ var firstA = a.first.toLowerCase(), firstB = b.first.toLowerCase(); if (firstA < firstB) return -1; // ascending if (firstA > firstB) return 1; return 0; // default (no sorting) }); application.output(aArr2); // [{first:Another,last:Buddy,role:Employee}, {first:Any,last:Buddy,role:Developer}, {first:Gary,last:Dotzlaw,role:Developer}, {first:No,last:Buddy,role:Manager}, {first:Some,last:Buddy,role:Employee}] // Sort the objects in the array by a property (ascending) var aArr3 = aArr.sort(function(a,b){ var firstA = a.first.toLowerCase(), firstB = b.first.toLowerCase(); if (firstB < firstA) return -1; // ascending if (firstB > firstA) return 1; return 0; // default (no sorting) }); application.output(aArr3); // [{first:Some,last:Buddy,role:Employee}, {first:No,last:Buddy,role:Manager}, {first:Gary,last:Dotzlaw,role:Developer}, {first:Any,last:Buddy,role:Developer}, {first:Another,last:Buddy,role:Employee}] |
The last one, you have seen before when we did .filter(). Here it is again, using the array of objects defined above.
1 2 | application.output(aArr.map(function(element){return element.first}).sort()); // [Another, Any, Gary, No, Some] |
.splice()
I haven’t seen too many people use the .splice() method, even when they could have. It’s very powerful, allowing you to replace existing values in an array with new ones, in the exact same positions. You specify the start position, the number of elements to replace, and provide the elements that will replace the existing data.
1 2 3 4 5 6 7 8 | var aArr = [1,2,3,4,5,6,7,8,9,0]; aArr.splice(0,5,"a", "b", "c","d","e"); application.output(aArr); // [a, b, c, d, e, 6.0, 7.0, 8.0, 9.0, 0.0] aArr = [1,2,3,4,5,6,7,8,9,0]; aArr2 = ["a", "b", "c","d","e"]; aArr.splice(0,5,aArr2.join()); application.output(aArr); //[a,b,c,d,e, 6.0, 7.0, 8.0, 9.0, 0.0] |
.dotzSortTwoArrays()
Okay, you got me, there is no such array method. Actually, its my own method that I wrote to sort two arrays in ascending order, using the first array as the ascending order. I have often used this when two arrays have related data at the same index locations, and I need to sort them and maintain the integrity of the related indexes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /** * Sorts two arrays in alpha asc keyed on the first array * * @author Gary Dotzlaw : Dotzlaw Consulting * * @param {Array} Array1 - First Array (used as the key for alpha asc) * @param {Array} Array2 - Second Array * * @returns {{a1:Array,a2:Array}Object} Returns an object containing the two sorted arrays */ function utils_arraySort(Array1, Array2) { var temp1, temp2, i = 0, bFound = true, iSize; while(bFound) { bFound = false; iSize = Array1.length; for(i = 0; i < iSize - 1; i++) { if(Array1[i] > Array1[i + 1]) { temp1 = Array1[i + 1]; temp2 = Array2[i + 1]; Array1[i + 1] = Array1[i]; Array2[i + 1] = Array2[i]; Array1[i] = temp1; Array2[i] = temp2; bFound = true; } } } return {a1: Array1, a2: Array2}; } |
Here is how you would use the method.
1 2 3 | var oObj = utils_arraySort(["Gary", "John", "Clint"], ["Dotzlaw", "Smith", "Eastwood"]); application.output(oObj.a1); // [Clint, Gary, John] application.output(oObj.a2); // [Eastwood, Dotzlaw, Smith] |
Conclusion
Whew! That was a lot, and if you stuck it through to the end, congratulations! Although it may seem to be a dry and boring topic, I don’t think you can truly get a mastery over Servoy, without fully understanding the basic nuances of the Javascript language. Arrays are as important as objects to master, and together, they will take your code to the next level. I appreciate you taking the time to work through this Servoy tutorial with me. I hope it made sense and you learned a thing or two. Now go out there and put these array methods to good use!
That concludes this Servoy tutorial. I hope you enjoyed it.
Bob Cusick
Jan 23, 2014 -
AWESOME, Gary! Thank you for that!