I am probably overlooking something or just being a noob but, I am having trouble being able to pass key-value pairs to a javascript function.
I am writing a filter module utilizing a Backbone.View and Backbone.Paginator.clientPager collection. I added a function to the clientPager called whereExpanded() which searches the entire collection without modifying/rendering anything.
Backbone.Paginator.clientPager.prototype.whereExpanded = function(attrs) {
return _.filter(this.origModels, function(model) {
var key;
for (key in attrs) {
if (attrs[key] !== model.get(key)) {
return false;
}
});
};
As you can see, it takes the attrs variable and loops over it. I want to be able to pass in key value pairs to this but, the key and value need to be dynamic.
collection.whereExpanded(filterByField, filterByValue)
filterByField is the attribute of the Backbone.Model I wish to filter by and filterByValue is the value of the attribute I want to filter. I have tried utilizing eval() but I cannot seem to get that to work either.
Any help woudl be greatly appreciated!
You don't need eval for this, you can build an object in better ways:
var attrs = { };
attrs[filterByField] = filterByValue;
And with a small bit of effort, you can let your function be called in various different ways:
whereExpanded({ k1: v1, k2: v2 });
whereExpanded('k1', v1);
whereExpanded('k1', v1, 'k2', v2);
You just need to parse arguments yourself with something like this:
argv = [].slice.call(arguments);
attrs = {};
if(argv.length == 1 && _(argv[0]).isObject()) {
attrs = argv[0];
}
else if(argv.length % 2 == 0) {
for(var i = 0; i < argv.length; i += 2) {
attrs[argv[i]] = argv[i + 1];
}
}
else {
throw 'Bad argument list';
}
That will leave you with the key/value pairs in attrs that your _.filter is expecting.
Argument parsing demo: http://jsfiddle.net/ambiguous/e5kkc/
It looks like eval() did work in the end. I was being a noob.
eval("collection.whereExpanded({" + filterByField + ": " + filterByValue + "})")
Related
I have the following script in Google Apps Script:
for(var i=0; i<lastCode; i++) {
var productCode = prodCodesArr[i];
for(var j=0; j<kelliLastCode; j++) {
var kelliProductCode = kelliCodesArr[j];
if(productCode == kelliProductCode) {
Logger.log('match found')
}
}
}
The 2 arrays are created dynamically. So the idea is (and I know there must be MUCH better ways to do this, but I am pretty new to this so bear with me) that I am setting i to the value of the first product code in one array and then looping through the other array whilst storing the product codes in this one to j. Now, I tried logging:
Logger.log(productCode + ' - ' + kelliProductCode);
And this worked and indeed, there were instances where productCode and kelliProduct code matched.
Yet my if statement above does not pick these up.
Again, I'm sure I've botched this entirely but any help would be greatly appreciated...
What's the point of the check? To determine which of your prodCodesArr items are also in kelliCodesArr? Why not parse kelliCodesArr just once, and then use hash lookups instead of array traversal? This will mean that you don't have to use nested for loops, which will scale very poorly as the inner loop size grows. An example (with some checks for assumptions on my part):
function foo() {
const kelliCodes = getKelliCodesArraySomehow();
const productCodes = getProductCodesArraySomehow();
// If these are 2D arrays, note that for `var a = ['help']; var b = ['help'];`
// `a` is never equal to `b` because they are not the exact same object in memory.
if (kelliCodes.length && Array.isArray(kelliCodes[0])) {
throw new TypeError("This SO answer was predicated on `kelliCodes` and `productCodes` being 1D arrays, but they aren't!");
}
const kelliLookup = kelliCodes.reduce(function (obj, kpc, idx) {
if (typeof kpc === 'object') {
console.log({message: "This SO answer assumed kpc was a string", kpc: kpc});
throw new TypeError("You probably want to store a property of this object, not the whole object");
}
obj[kpc] = idx;
return obj;
}, {});
var productsAlsoInKelliCodes = productCodes.filter(function (pc) {
return kelliLookup.hasOwnProperty(pc);
});
productsAlsoInKelliCodes.forEach(function (pc) {
Logger.log("The index of this product code %s in kelliCodes is %s", pc, kelliLookup[pc]);
});
}
If your ___codes arrays are 2D arrays, you should flatten them before comparison, as comparing an Array instance to another Array instance will always return false, even if they contain the same element primitives--they aren't the exact same Array instance:
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
Array#forEach
Array#map
In JS, which is faster: Object's "in" operator or Array's indexof?
Javascript: what lookup is faster: array.indexOf vs object hash?
I'm sure there are more.
Something like this might help you to see what's happening:
function compareA(prodCodesArr,kelliCodesArr) {
var html="";
for(var i=0;i<prodCodesArr.length;i++) {
for(var j=0;j<kelliCodesArr.length;j++) {
if(productCodesArr[i]==kelliCodesArr[j]) {
html+=Utilities.formatString('Matched: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}else{
html+=Utilities.formatString('No-Match: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}
}
}
var userInterface=HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Comparing')
}
I would like to create a view with map / reduce functions that return the latest values from several documents with same id, seems I can return the latest document giving with the xyz id based on the update_datetime, not sure how to get the latest value like field1 and field2, thank you
doc1: id='xyz', field1='v1.0', field2='v2.0', field3='v3.0', status='NEW', update_datetime='2015-04-28'
doc2: id='xyz', field2='v2.1', field3='v3.0', status='CHANGED', update_datetime='2015-05-01'
doc3: id='xyz', field3='v3.1', status='CLOSED', update_datetime='2015-05-10'
define a view with map / reduce that, given a key value 'xyz', returns the following document:
id='xyz', field1='v1.0', field2='v2.1', field3='v3.1', status='CLOSED', update_datetime='2015-05-10'.
I have a feeling that the data model you have could be improved but I do not know enough about your use case to comment on that really. Therefore I would suggest a reduce function that will perform a merge of the documents using newer as an override for existing properties unless a property does not exist.
Here is the content of map and reduce functions (btw, I have changed the name of one of the fields from update_datetime to updated. Also please bear in mind that id and _id are different - might be worth renaming your own id field to something more distinguishable from couchdb's doc id field):
// MAP
function(doc) {
emit(doc.id, doc);
}
// REDUCE
function(keys, values, rereduce) {
if (values.length > 1) {
var mergedDoc = values[0];
for (var i = 1; i < values.length; i++) {
var newDoc = values[i];
var updatingWithNewer = newDoc.updated >= mergedDoc.updated;
for (key in newDoc) {
if (key != "_id" && key != "id") {
if (updatingWithNewer || mergedDoc[key] == undefined) {
mergedDoc[key] = newDoc[key];
}
}
}
}
return mergedDoc;
} else {
return values[0];
}
}
Performing level 1 reduce will give you expected result. Hope it helps.
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 = [[],[],[]]
Is there an equivalent of IEnumerable.Any(Predicate<T>) in JavaScript or jQuery?
I am validating a list of items, and want to break early if error is detected. I could do it using $.each, but I need to use an external flag to see if the item was actually found:
var found = false;
$.each(array, function(i) {
if (notValid(array[i])) {
found = true;
}
return !found;
});
What would be a better way? I don't like using plain for with JavaScript arrays because it iterates over all of its members, not just values.
These days you could actually use Array.prototype.some (specced in ES5) to get the same effect:
array.some(function(item) {
return notValid(item);
});
You could use variant of jQuery is function which accepts a predicate:
$(array).is(function(index) {
return notValid(this);
});
Xion's answer is correct. To expand upon his answer:
jQuery's .is(function) has the same behavior as .NET's IEnumerable.Any(Predicate<T>).
From http://docs.jquery.com/is:
Checks the current selection against an expression and returns true, if at least one element of the selection fits the given expression.
You should use an ordinary for loop (not for ... in), which will only loop through array elements.
You might use array.filter (IE 9+ see link below for more detail)
[].filter(function(){ return true|false ;}).length > 0;
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I would suggest that you try the JavaScript for in loop. However, be aware that the syntax is quite different than what you get with a .net IEnumerable. Here is a small illustrative code sample.
var names = ['Alice','Bob','Charlie','David'];
for (x in names)
{
var name = names[x];
alert('Hello, ' + name);
}
var cards = { HoleCard: 'Ace of Spades', VisibleCard='Five of Hearts' };
for (x in cards)
{
var position = x;
var card = card[x];
alert('I have a card: ' + position + ': ' + card);
}
I suggest you to use the $.grep() method. It's very close to IEnumerable.Any(Predicate<T>):
$.grep(array, function(n, i) {
return (n == 5);
});
Here a working sample to you: http://jsfiddle.net/ErickPetru/BYjcu/.
2021 Update
This answer was posted more than 10 years ago, so it's important to highlight that:
When it was published, it was a solution that made total sense, since there was nothing native to JavaScript to solve this problem with a single function call at that time;
The original question has the jQuery tag, so a jQuery-based answer is not only expected, it's a must. Down voting because of that doesn't makes sense at all.
JavaScript world evolved a lot since then, so if you aren't stuck with jQuery, please use a more updated solution! This one is here for historical purposes, and to be kept as reference for old needs that maybe someone still find useful when working with legacy code.
Necromancing.
If you cannot use array.some, you can create your own function in TypeScript:
interface selectorCallback_t<TSource>
{
(item: TSource): boolean;
}
function Any<TSource>(source: TSource[], predicate: selectorCallback_t<TSource> )
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (let i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
} // End Function Any
Which transpiles down to
function Any(source, predicate)
{
if (source == null)
throw new Error("ArgumentNullException: source");
if (predicate == null)
throw new Error("ArgumentNullException: predicate");
for (var i = 0; i < source.length; ++i)
{
if (predicate(source[i]))
return true;
}
return false;
}
Usage:
var names = ['Alice','Bob','Charlie','David'];
Any(names, x => x === 'Alice');
If I have a JavaScript object such as:
var x = {foo: 42, bar: {fubar: true}}
then I can get the value true with var flag = x.bar.fubar. I'd like to be able to separate out and store the path "bar.fubar", then evaluate it dynamically. Something like:
var paths = ["bar.fubar", ...];
...
var flag = evalPath( x, paths[0] );
Obviously I could write a simple parser and evaluator for a basic path expression grammar. But under DRY principles I wonder if there's already an existing way to do something like evalPath built-in to JavaScript, or a small library that would do the job? I also anticipate needing array indexes in the path expression in future.
Update Just to be clear, I'm not asking for code samples - my question is whether there's existing code (built-in or library) I can re-use. Thanks to the contributors below for suggestions of code samples anyway! Note that none of them handle the array index requirement.
Doing a quick search, I came across JSONPath. Haven't used it at all, but it looks like it might do what you want it to.
Example usage:
var x = {foo: 42, bar: {fubar: true}}
var res1 = jsonPath(x, "$.bar.fubar"); // Array containing fubar's value
Why not try something like
function evalPath(obj, path)
{
var rtnValue = obj;
// Split our path into an array we can iterate over
var path = path.split(".");
for (var i = 0, max=path.length; i < max; i++)
{
// If setting current path to return value fails, set to null and break
if (typeof (rtnValue = rtnValue[path[i]]) == "undefined")
{
rtnValue = null;
break;
}
}
// Return the final path value, or null if it failed
return rtnValue;
}
Not tested, but it should work fairly well. Like XPath, it will return null if it can't find what it's looking for.
JavaScript provides eval, but I don't recommend it.
like
function locate(obj, path) {
var p = path.split("."), a = p.shift();
if(a in obj)
return p.length ? locate(obj[a], p.join(".")) : obj[a];
return undefined;
}
locate(x, "bar.fubar")
this works on the right only, of course
You could try something like this. I can't really think of a situation where it would be appropriate to store paths this way though.
function evalPath(obj, path) {
var pathLevels = path.split('.');
var result = obj;
for (var i = 0; i < pathLevels.length; i++) {
try {
result = result[pathLevels[i]];
}
catch (e) {
alert('Property not found:' + pathLevels[i]);
}
}
return result;
}
The alert is really only there for debugging purposes. You may want to return null or something.
How about:
evalPath = function(obj, path) {
if (path[0] === "[") {
return eval("obj" + path);
} else {
return eval("obj." + path);
}
};
This has the advantage that it works for arbitrary strings:
evalPath([1,2,3], "[0]"); => 1
evalPath({a:{b:7}}, "a.b"); => 7
This, of course, only works if you really trust your input.