Call function only once after a map() - javascript

I am having an issue on Nodejs, I need to call a function only once when item.IS_RACING === 1
look
_.map(recordsets, function(items) {
return _.map(items, function(item) {
if (item.IS_RACING === 1) {
_this.getRacing();
}
});
});
I have that _this.getRacing(); which is being called everytime the conditional is true, but if there is 20 items with IS_RACING === 1, so the function _this.getRacing(); is going to be call 20 times. I need something like, once the app detects when the first IS_RACING === 1 comes up, then fires _this.getRacing(); only once.
Any recommendation ?

As Pointy pointed out (sorry) in the comments, you really don't want to use map() to do this.
Think of the problem in terms of how you would explain it to another developer.
If any of the record sets has an item that is racing, I want to call getRacing().
Now, write code that represents your intent.
var somethingIsRacing = _.some(recordsets, function(items) {
return _.some(items, function(item) {
return item.IS_RACING === 1;
});
});
if(somethingIsRacing) {
_this.getRacing();
}
This code follows a principle called Command-Query Separation, where you first query to find the information you need using a functional style of programming, then you perform actions that will have side-effects using an imperative programming style.

A flag variable usually does the trick:
var getRacingCalled = false;
_.map(recordsets, function(items) {
return _.map(items, function(item) {
if (item.IS_RACING === 1 && !getRacingCalled) {
_this.getRacing();
getRacingCalled = true;
}
});
});

Try to do it with a closure:
var closure = (function() {
var fired = false;
return function (item) {
if (!fired && item.IS_RACING === 1) {
fired = true;
_this.getRacing();
}
};
})();
_.map(recordsets, function(items) {
return _.map(items, closure(item));
});

Related

Javascript: move objects from one array to another: Best approach?

I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];

Implementing eachChild for a specefic case

I have a few places in my code that are very similar to this snippet:
tag_iter = hold_tags_el.firstChild;
do {
if (tag_iter === null) {
hold_tags_el.appendChild(paragraph_el);
break;
}
if (par_el.innerHTML < tag_iter.innerHTML) {
hold_tags_el.insertBefore(paragraph_el, tag_iter);
break;
}
if (tag_iter === hold_tags_el.lastChild) {
NS.insertAfter(tag_iter, paragraph_el);
break;
}
tag_iter = tag_iter.nextSibling;
} while (tag_iter !== null);
This can be abstracted to:
tag_iter = ref_el.firstChild;
do {
// loop logic
tag_iter = tag_iter.nextSibling;
} while (tag_iter !== null);
In a function form this would look like:
The Call:
eachChild(par_el, function (tag_iter, par_el) {
// loop logic
});
The Definition:
NS.eachChild = function (par_el, func, context) {
var iter_el = par_el.firstChild,
result;
do {
result = func.call(context, iter_el, par_el);
if (result) {
break;
}
iter_el = iter_el.nextSibling;
} while (iter_el !== null);
}
Is there a library that implements this pattern / idiom?
What improvements can be made to eachChild?
Are there any errors in eachChild?
Applying the idiom we have:
Snippet A
NS.eachChild(el, function(tag_iter, par_el){
// first
if (tag_iter === null) {
par_el.appendChild(paragraph_el);
return true;
}
// middle
if (par_el.innerHTML < tag_iter.innerHTML) {
par_el.insertBefore(paragraph_el, tag_iter);
return true;
}
// last
if (tag_iter === hold_tags_el.lastChild) {
par_el.appendChild(paragraph_el);
return true;
}
});
What improvements can be made?
Many. Your snippet with its do-while loop and the many breaks is overly complicated and hard to understand. It can be simplified to
var tag_iter = hold_tags_el.firstChild,
search = par_el.innerHTML;
while (tag_iter !== null && search >= tag_iter.innerHTML)
tag_iter = tag_iter.nextSibling;
hold_tags_el.insertBefore(paragraph_el, tag_iter);
Notice that insertBefore with null as second argument, insertAfter(lastChild) and appendChild do exactly the same thing.
With that simplification, you don't need that eachChild function any more. But maybe a little different one:
NS.findChild = function(parent, condition) {
var child = parent.firstChild;
for (var i=0; child!==null && condition(child, i); i++)
child = child.nextSibling;
return child;
};
// then simply:
var el = NS.findChild(hold_tags_el, function(tag_iter) {
return tag_iter.innerHTML < par_el.innerHTML;
});
hold_tags_el.insertBefore(paragraph_el, el);
Is there a library that implements this pattern / idiom?
I don't know any. But there are many libs with generic iterator methods (some of them with break functionality) that can easily be applied on childNodes collections.
Are there any errors in eachChild?
It calls the callback even when there is no firstChild (with null as argument). That's at least unconventional, if not wrong - not what you would expect from an iteration. If you think to need it, this should better be made a separate case (a separate callback); otherwise it requires an extra condition in the callback. However in the given usecase you do not need it, as that is a search - see the findChild function above - where eachChild is inappropriate.
What improvements can be made to eachChild?
Additionally to parEl maybe a counter argument might be nice - check the signature of the standard forEach Array method.

jquery boolean iterators

...or what's the proper name for some() and every(). Basically, I'm looking for a function or a plugin that would allow me to write something like:
okay = $("#myForm input").every(function() {
return $(this).val().length > 0
})
or
hasErrors = $(listOfUsers).some(function() {
return this.errorCount > 0;
})
You got the idea.
(Before the what-have-you-tried squad arrives, I googled and found jquery.arrayUtils, but that code doesn't look convincing to me).
A simple, straightforward implementation:
$.fn.some = function(callback) {
var result = false;
this.each(function(index, element) {
// if the callback returns `true` for one element
// the result is true and we can stop
if(callback.call(this, index, element)) {
result = true;
return false;
}
});
return result;
};
$.fn.every = function(callback) {
var result = true;
this.each(function(index, element) {
// if the callback returns `false` for one element
// the result is false and we can stop
if(!callback.call(this, index, element)) {
result = false;
return false;
}
});
return result;
};
With ES5, arrays already provide the methods every and some, so you could achieve the same with built in methods:
okay = $("#myForm input").get().every(function(element) {
return $(element).val().length > 0
});
but it won't work in older IE version without HTML5 shim.
You can do something like this
okay = $("#myForm input").each(function() {
return $(this).val().length > 0
})
okay = $("#myForm input").find('class').each(function() {
return $(this).val().length > 0
})

Javascript function formatting

I'm concerned my if condition is not correctly formatted.
Does this look right to you?
function validateCoupon( form ){
if (form.textCoupon.value.length ){
if (form.textCoupon.value.toLowerCase() == "Item01") {
_gaq.push(['_trackEvent', 'Coupon', 'Activated', 'Item01']);
}
if (form.textCoupon.value.toLowerCase() == "Item02") {
_gaq.push(['_trackEvent', 'Coupon', 'Activated', 'Item02']);
}
$.get( "/include/checkCoupon.php", { coupon: form.textCoupon.value }, validateShipping );
}
else {
form.textCoupon.style.border = '';
validateShipping( "yes" );
}
return false;
}
Well, something appears to be a redundancy: form.textCoupon.value could be Item01 or Item02. If it's one it couldn't be the other, so I'd suggest you a switch statement.
Another problem is if you call .toLowerCase() this never will return Item01 but item01, and string equality is case-sensitive. Either call this function to both parts of condition or just don't use it.
If this were my code, this would be how I wrote it:
var validateCoupon = function (form) {
var textCoupon = form.textCoupon,
value = textCoupon.value,
track = function (value) {
_gaq.push(['_trackEvent', 'Coupon', 'Activated', value]);
};
if (value.length) {
if (value === 'Item01' || value === 'Item02') {
track(value);
}
$.get('/include/checkCoupon.php', { coupon: value }, validateShipping);
} else {
textCoupon.style.border = '';
validateShipping('yes');
}
return false;
};
Formatting of the source code is irrelevant to the JavaScript runtime. It's just a matter of personal taste.
Avoid tabs in indentation: they are not rendered identically on all platforms.

Alternative to jQuery's .toggle() method that supports eventData?

The jQuery documentation for the .toggle() method states:
The .toggle() method is provided for convenience. It is relatively straightforward to implement the same behavior by hand, and this can be necessary if the assumptions built into .toggle() prove limiting.
The assumptions built into .toggle have proven limiting for my current task, but the documentation doesn't elaborate on how to implement the same behavior. I need to pass eventData to the handler functions provided to toggle(), but it appears that only .bind() will support this, not .toggle().
My first inclination is to use a flag that's global to a single handler function to store the click state. In other words, rather than:
$('a').toggle(function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
});
do this:
var clicks = true;
$('a').click(function() {
if (clicks) {
alert('odd number of clicks');
clicks = false;
} else {
alert('even number of clicks');
clicks = true;
}
});
I haven't tested the latter, but I suspect it would work. Is this the best way to do something like this, or is there a better way that I'm missing?
Seems like a reasonable way to do it... I'd just suggest that you make use of jQuery's data storage utilities rather than introducing an extra variable (which could become a headache if you wanted to keep track of a whole bunch of links). So based of your example:
$('a').click(function() {
var clicks = $(this).data('clicks');
if (clicks) {
alert('odd number of clicks');
} else {
alert('even number of clicks');
}
$(this).data("clicks", !clicks);
});
Here is a plugin that implements an alternative to .toggle(), especially since it has been removed in jQuery 1.9+.
How to use:
The signature for this method is:
.cycle( functions [, callback] [, eventType])
functions [Array]: An array of functions to cycle between
callback [Function]: A function that will be executed on completion of each iteration. It will be passed the current iteration and the output of the current function. Can be used to do something with the return value of each function in the functions array.
eventType [String]: A string specifying the event types to cycle on, eg. "click mouseover"
An example of usage is:
$('a').cycle([
function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
}
]);
I've included a demonstration here.
Plugin code:
(function ($) {
if (!Array.prototype.reduce) {
Array.prototype.reduce = function reduce(accumulator) {
if (this === null || this === undefined) throw new TypeError("Object is null or undefined");
var i = 0,
l = this.length >> 0,
curr;
if (typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
throw new TypeError("First argument is not callable");
if (arguments.length < 2) {
if (l === 0) throw new TypeError("Array length is 0 and no second argument");
curr = this[0];
i = 1; // start accumulating at the second element
} else curr = arguments[1];
while (i < l) {
if (i in this) curr = accumulator.call(undefined, curr, this[i], i, this);
++i;
}
return curr;
};
}
$.fn.cycle = function () {
var args = Array.prototype.slice.call(arguments).reduce(function (p, c, i, a) {
if (i == 0) {
p.functions = c;
} else if (typeof c == "function") {
p.callback = c;
} else if (typeof c == "string") {
p.events = c;
}
return p;
}, {});
args.events = args.events || "click";
console.log(args);
if (args.functions) {
var currIndex = 0;
function toggler(e) {
e.preventDefault();
var evaluation = args.functions[(currIndex++) % args.functions.length].apply(this);
if (args.callback) {
callback(currIndex, evaluation);
}
return evaluation;
}
return this.on(args.events, toggler);
} else {
//throw "Improper arguments to method \"alternate\"; no array provided";
}
};
})(jQuery);

Categories

Resources