Extend a JavaScript object with a default function - javascript

In google app scripts I have a one dimensional data array that I can get values from like this:
data[0]
I'd love to be able to pass in the column name instead like this:
data("A")
That way I don't have to convert letters into their array position. So I'd like to extend the array object (extending isn't really risky since it's running in an isolated script environment).
I know I can add a function to the array prototype using this letter to number function and this object extension question like this:
Array.prototype.byCol = function(colName) {
return this[getColumnNumber(colName) - 1];
}
function getColumnNumber(str) {
var out = 0, len = str.length;
for (pos = 0; pos < len; pos++) {
out += (str.charCodeAt(pos) - 64) * Math.pow(26, len - pos - 1);
}
return out;
}
var data = [1,2,3,4];
document.write(data.byCol("B"));
But this is a slightly bulkier calling syntax than I wanted.
Based on this question on default functions, it looks like it's possible to assign a default function to an object, but they're doing so by just creating a function object like this:
var test = new func(function() {
// do something
});
Can I get extend the array so that it will execute a default function when called as a method?

Put simply, you can't make something into a function if it's not already a function, and you can't really extend arrays.
What you can do is create a wrapper function that wraps an array and provides the functionality you want, and also include the ability to get back the original array if you need it:
var wrapper = (function() {
function getColumnNumber(str) {
return Array.prototype.reduce.call(str.toUpperCase(), function (t, c) {
return 26 * t + c.charCodeAt(0) - 64;
}, 0) - 1;
}
return function(arr) {
return function(col, val) {
if (arguments.length === 0) {
return arr;
}
if (arguments.length > 1) {
arr[getColumnNumber(col)] = val;
}
return arr[getColumnNumber(col)];
};
};
})();
var w = wrapper([10, 20, 30, 40, 50]);
snippet.log(w('D')); // 40
w('D', 33); // set value
snippet.log(w('D')); // 33
w()[3] = 42; // access underlying array directly
w().push(60);
snippet.log(w('D')); // 42
snippet.log(w('F')); // 60
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Related

Javascript Array.push isn't working in get method

I am trying to implement a range property on a sequence object with the following code:
function RangeSeq(from, to) {
this.array = [];
this.from = from;
this.to = to;
this.arraySeq = new ArraySeq(this.range);
}
Object.defineProperty(RangeSeq.prototype, "range", {
get: function() {
for (var i = this.from; i <= this.to; i++) {
array.push[i];
console.log(this.array)
}
return array;
}
});
However, after running the code above, the array object still remains empty. Why is this so and what's the right way to solve this?
You used square brackets and not referring to this.array. It should be
this.array.push(i);

JS array sort with regex

My objective is to sort an array based on the priority of JS object that uses regular expressions. Here's what I have:
JS objects defined:
var rx_CondoIndicator = new RegExp(/\bCONDO(MINIUM)?(?!S)/);
var rx_TownhouseIndicator = new RegExp(/\bTOWN\s*(HOUSE|HOME)(?!S)/);
var rx_TimeshareIndicator = new RegExp(/\bTIMESHARE(?!S)/);
Sort objects defined:
var so_UnitKeywordIndicator = {
rx_CondoIndicator: 1,
rx_TownhouseIndicator: 2,
rx_TimeshareIndicator: 3
};
arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13']
The following logic ignores the sort object priority and does not sequence the array in the desired order which is "CONDO #13", "TOWNHOUSE 407", "TIMESHARE A-1"
arrUnitNumber.sort(function(x,y){
return so_UnitKeywordIndicator[x] - so_UnitKeywordIndicator[y];
})
Please note that only a fraction of the JS sort objects are shown for brevity.
If this is not the best solution for the problem, I'm open to any suggestions being a novice at JS.
The main problem is that you're never using your regular expressions for anything in the sort loop, and x and y will be entries from the array ('TIMESHARE A-1' and similar), which don't exist on so_UnitKeywordIndicator as properties. So so_UnitKeywordIndicator[x] will always be undefined.
It looks like you want to use the regular expressions to categorize the strings, and then sort them based on the relatives values in so_UnitKeywordIndicator. So you'll need to test the strings against the regular expressions.
If you have to start from those strings, I think I'd probably approach it like this (removing so_UnitKeywordIndicator):
arrUnitNumber.sort(function(x,y){
return getSortingKey(x) - getSortingKey(y);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
Live Example:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
var arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13'];
arrUnitNumber.sort(function(x, y) {
return getSortingKey(x) - getSortingKey(y);
});
arrUnitNumber.forEach(function(entry) {
snippet.log(entry);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
If there are a lot of entries, though, that's inefficient because it has to recalculate the sorting key every time two entries in the array are compared, and so does it repeatedly even for the same value (potentially). So if there are a large number of entries, you're probably best off building an array of objects with the sort key on them instead:
var unitObjects = arrUnitNumber.map(function(entry) {
return {
str: entry,
key: getSortingKey(entry)
};
});
unitObjects.sort(function(x, y){
return x.key - y.key;
});
Then either just use that array, or if you want strings again, just map at the end:
arrUnitNumber = unitObjects.map(function(entry) {
return entry.str;
});
But again, only if there are a lot (lot) of entries in the array.
Live Example:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
var arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13'];
var unitObjects = arrUnitNumber.map(function(entry) {
return {
str: entry,
key: getSortingKey(entry)
};
});
unitObjects.sort(function(x, y){
return x.key - y.key;
});
unitObjects.forEach(function(entry) {
snippet.log(entry.str);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Side note: You don't need (or want) the new RegExp( part where you're defining your regular expressions, JavaScript is unusual in that it has regular expression literals (like string literals); that's what you have in your /.../:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
First, put all of your regexes in an array. The order in which they are listed will determine their rank, where the first element is the highest.
var so_UnitKeywordIndicator = [rx_CondoIndicator, rx_TownhouseIndicator, rx_TimeshareIndicator];
Now this function will give the rank for a given unit.
function getSortRank(unit) {
for (var i = 0; i < so_UnitKeywordIndicator.length; i++) {
if (so_UnitKeywordIndicator[i].test(unit)) {
return i;
}
}
return so_UnitKeywordIndicator.length;
}
Finally, you can then use this function to sort your array of units like this:
var sortedUnits = arrUnitNumber.sort(function(x, y) {
return getSortRank(x) - getSortRank(y);
});
just for sharing my problem & solution. I need to order my format data by tag.
const nameTagDatas = ["name(ddd)", "name(ccc)", "name(bbb)", "name(aaa)"];
const tagOrder = ["aaa", "bbb", "ccc", "ddd"];
nameTagDatas.sort(
(a, b) =>
tagOrder.findIndex((e) => a.search(new RegExp("\\(" + e + "\\)")) != -1) -
tagOrder.findIndex((e) => b.search(new RegExp("\\(" + e + "\\)")) != -1)
);
console.log(nameTagDatas) // [ 'name(aaa)', 'name(bbb)', 'name(ccc)', 'name(ddd)' ]
extract function
nameTagDatas.sort(
(a, b) => findIndexByTag(tagOrder, a) - findIndexByTag(tagOrder, b)
);
function findIndexByTag(_orderArr, _searchStr) {
return _orderArr.findIndex(
(e) => _searchStr.search(new RegExp("\\(" + e + "\\)")) != -1
);
}
You can use .match(/<regex>/)
Here is JS
let wordsArr = ['2.World', '1.Hello'];
const regex = /\d/
let answer = wordsArr.sort((a,b) => {
return a.match(regex) - b.match(regex);
});
console.log('answer', answer);
Here is a fiddle.

How do I change the Array in place when prototyping

I'm writing a custom sort function that I'm prototyping into Array. (PLEASE don't post answers explaining to me how I shouldn't bother prototyping into Array for whatever reason you feel prototyping into Array isn't a good idea).
so, my method looks like this:
//method
Array.prototype.mySort = function(memberName, ascOrDesc){
var labelRow = this.shift();
var ret = this.sort((function (a,b){
if(ascOrDesc > 0)
return (a[memberName] > b[memberName])?1:-1;
return (a[memberName] < b[memberName])?1:-1;
}));
ret.unshift(labelRow)
return ret;
}
Notice how this.shift() will affect the Array IN PLACE.
However, I'm not clear on how this is accomplished. If I wanted to write my own myShift method, at some point I'd need to say something to the effect of
this = this.myShift();
which is obviously illegal.
So, I'm trying to understand how shift() gets access to the array's members and is able to remove the first one in-place. And if I'm allowed to do something analogous, or if this is somehow baked in and not available to me to use.
You can access the array using this inside the method.
You can for example implement the shift method as:
Array.prototype.myShift = function() {
if (this.length == 0) return null;
var result = this[0];
for (var i = 1; i < this.length; i++) {
this[i-1] = this[i];
}
this.length--;
return result;
};
The problem is that you can't assign to this. This means you can't do things like this:
Array.prototype.myShift = function() {
this = this.slice(1);
};
This is because Array.prototype.slice returns a new array and does not modify the old array. Other methods, however, such as Array.prototype.splice, do modify the old array. So you can do something like this:
Array.prototype.myShift = function() {
return this.splice(0, 1)[0];
};
This will have exactly the same behaviour as the standard Array.prototype.shift method. It modifies the current array, so you can do this:
var labelRow = this.myShift();

Looping through an object with incremental property names

I'm trying to loop through an object like you would an array. I'm struggling to append the loop counter to the variable name.
I have an object like this (output with dump(), which I found here):
object(2): {
elem0: array(4): {
[0]: string(27): "http://placehold.it/300x300"
[1]: string(3): "0.8"
[2]: string(4): "-150"
[3]: string(3): "200"
}
elem1: array(4): {
[0]: string(27): "http://placehold.it/300x300"
[1]: string(3): "0.6"
[2]: string(3): "-70"
[3]: string(3): "458"
}
}
Here's how I'm trying to loop through it:
jQuery(document).ready(function($) {
// Provides object-measuring functionality
Object.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
// Returns the number of objects in my object
var size = Object.size(window.depthElems);
/*
This is where I'm having difficulty.
I would like to use window.depthElems.elem0,
then window.depthElems.elem1, etc.
*/
for (var i = 0; i < size; i++) {
$('.wrapper').append('<img src="' + window.depthElems.elem+i+[0] + '" />');
}
});
I will, for the sake of argument, also provide my question as answer. You can use:
for(element in window.depthElems) {
if(window.depthElems.hasOwnProperty(element)) {
$('.wrapper').append('<img src="' + window.depthElems[element] + '" />');
}
}
This is not only more elegant, but also requires far less code. Of course if there is a reason to use the other code, please say so.
Note: This code is edited to also include the ability to read 'arrays', however the question was to make it work with 'objects'. If you use 'objects' the 'hasOwnProperty' check is superfluous.
Note #2: You can also use var hasOwn = Object.prototype.hasOwnProperty; like Azder said, which is a nice safeguard.
I apologize if my answer is over the top, I just like to prevent further hurt by miss-using JS (which I have experienced a lot) .
jQuery(document).ready(function($) {
var i; // there is no block scope in JS, so better to be clear and define i here
var $wrapper; // also
// Changing the JS built-in objects is problematic most of the time
// You should learn from jQuery and do wrapping instead
// Or at least just a good namespasing like:
// MyFramework.objectSize = function (obj) {}
Object.size = function(obj) {
var size = 0, key;
var hasOwn = Object.prototype.hasOwnProperty; // will explain further down
for (key in obj) {
// if obj has redifined hasOwnProperty = function(){ return false; }?
// it's better to use hasOwn like this if(hasOwn.call(obj,key)) {}
// and please do use braces even if only 1 statement
if(hasOwn.call(obj,key)) size++;
}
return size;
};
// Returns the number of objects in my JSON object
var size = Object.size(window.depthElems);
$wrapper = $('.wrapper'); // cached so jQuery doesn't search for it each iteration
// i is scoped to the whole function anyways
for (i = 0; i < size; i++) {
// $.each even guards you of the changing DOM which can cause
// infinite loops (you don't have that problem here, but... good to know
$.each(window['depthElems'+i],function(index,element){
$wrapper.append('<img src="' + element + '" />');
});
}
});
Also, since you already make objects named elem1, elem2, elem3,... you might as well use a two dimensional array, like window.depthElems = [[],[],[]]

How to find index of object in a JavaScript array with jQuery

I am trying to find the index of an object in an array in jquery.
I cannot use jQuery.inArray because i want to match objects on a certain property.
I am using:
jQuery.inObjectArray = function(arr, func)
{
for(var i=0;i<arr.length;i++)
if(func(arr[i]))
return i;
return -1;
}
and then calling:
jQuery.inObjectArray([{Foo:"Bar"}], function(item){return item.Foo == "Bar"})
Is there a built in way?
Not sure why each() doesn't work for you:
BROKEN -- SEE FIX BELOW
function check(arr, closure)
{
$.each(arr,function(idx, val){
// Note, two options are presented below. You only need one.
// Return idx instead of val (in either case) if you want the index
// instead of the value.
// option 1. Just check it inline.
if (val['Foo'] == 'Bar') return val;
// option 2. Run the closure:
if (closure(val)) return val;
});
return -1;
}
Additional example for Op comments.
Array.prototype.UContains = function(closure)
{
var i, pLen = this.length;
for (i = 0; i < pLen; i++)
{
if (closure(this[i])) { return i; }
}
return -1;
}
// usage:
// var closure = function(itm) { return itm.Foo == 'bar'; };
// var index = [{'Foo':'Bar'}].UContains(closure);
Ok, my first example IS HORKED. Pointed out to me after some 6 months and multiple upvotes. : )
Properly, check() should look like this:
function check(arr, closure)
{
var retVal = false; // Set up return value.
$.each(arr,function(idx, val){
// Note, two options are presented below. You only need one.
// Return idx instead of val (in either case) if you want the index
// instead of the value.
// option 1. Just check it inline.
if (val['Foo'] == 'Bar') retVal = true; // Override parent scoped return value.
// option 2. Run the closure:
if (closure(val)) retVal = true;
});
return retVal;
}
The reason here is pretty simple... the scoping of the return was just wrong.
At least the prototype object version (the one I actually checked) worked.
Thanks Crashalot. My bad.

Categories