jQuery's .add on an empty set? - javascript

I am working on a plugin with quite a few options and as a consequence I am trying to keep track of a set of elements and put them in a variable. The variable cannot be empty (but that is of no concern here). Let's say there are only two options, then the variable will hold one or two elements as a jQuery object, i.e. $("#el1, #el2"). I tried the following, but the result of adding is still $([]).
var track = $([]);
someFunc() {
if (option1) track.add("#el1");
if (option2) track.add("#el2");
}
// result is `$([])`
Note that I don't want an array back, but a jQuery selector as I posted in the example above.

You could first sort out which elements/selectors you need.
And then use these to init the track variable with an jQuery object passing in all the relevant selectors.
var track = someFunc();
// you would have to check the length of `track` first as it may be only an empty array (length == 0) and no real jQuery object
if (track.length) {
//...
}
// returns a jQuery object with all the matched elements
// or an empty array if there is no relevant selector
function someFunc() {
// place to store the selectors
var selectors = [];
// store the relevant selectors in <selectors>
if (option1) selectors.push("#el1");
if (option2) selectors.push("#el2");
// if there is at least one selector in <selectors>
if (selectors.length > 0) {
// create a jQuery object of them and return it
return $(selectors.join())
} else {
// otherwise we return an empty array
// this allows us to use .length in both cases
return [];
}
// or always return a jQuery object
// return $(selectors.join());
}

Use a array join on coma:
var elements = [];
elements.push('#one');
console.log(elements.join(','));
$(elements.join(','));
https://jsfiddle.net/8xx8x1xe/

Related

Using parent() in a for loop

I am creating a chrome extension that blocks all porn results on all torrent search engine sites.
So I am trying to retrieve the name of the torrents and check them against the array of strings containing blocked (adult/porn) words that I created. If it matches the array word then it should set the display of the parent element to none. But parent() from jQuery doesn't seem to work around this in a for loop. This is the code that I am using.
// 'blockedWords' is the array.
// '$("dl dt")' contains the words that I am checking against strings from
// the array 'blockedWords'.
for (var i = 0; i < $("dl dt").length; i++) {
for (var j = 0; j < blockedWords.length; j++) {
if($("dl dt")[i].innerText.indexOf(blockedWords[j]) > -1){
$(this).parent().style.display= "none"; // 1st Method or
$("dl dt")[i].parent().style.display= "none"; // 2nd Method
}
}
}
// 1st Method shows the error 'Cannot set property 'display' of undefined'
// 2nd Method shows the error '$(...)[i].parent is not a function'
// '$("dl dt")[i].parent().style.display' doesn't work but
// '$("dl dt").parent().style.display' doesn't work either
// '$("dl dt")[i].style.display' works perfectly without parent().
I have also tried 'parents()'.
Any help will be appreciated :).
As a newbie, I am also open to any other suggestions or recommendations.
And I would be really grateful if you could explain your code as well :)
And by the way, can you believe there are more than 500 porn companies out there :o :P :D
Since you have jQuery, you can avoid using nested for-loops using jQuery's filter() and JavaScript reduce(s,v):
// Filter function removes elements that return a false/falsey value like 0
$("dl dt").filter(function() {
// Save current element's innerText so we can use it within the reduce function
var str = $(this).text();
// Return sum of reduce function
return blockedWords.reduce(function(s, v) {
// For each item in blockedWords array, check whether it exists in the string. Add to total number of matches.
return s + !!~str.indexOf(v);
}, 0); // 0 = intial value of reduce function (number of matches)
}).parent().hide(); // Hide elements which pass through the filter function
Demo:
var blockedWords = [
'shit', 'fuck', 'sex'
];
$("dl dt").filter(function() {
var str = $(this).text();
return blockedWords.reduce(function(s, v) {
return s + !!~str.indexOf(v);
}, 0);
}).parent().hide();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<dl><dt>this is shit</dt></dl>
<dl><dt>this is okay</dt></dl>
<dl><dt>fuck this</dt></dl>
<dl><dt>no problem</dt></dl>
<dl><dt>sex videos</dt></dl>
EDIT: I apologize for the earlier answer if you saw it, as it was incomplete. I have also added a snippet for demonstration purposes. For further explanation of the reduce algorithm, check this answer out (basically it converts the value of indexOf to either a 0 or 1, because indexOf returns -1 if not found, or another 0-indexed integer of the position if found).
JQuery's parent function returns a JQuery object with the parent element inside of it. If you want to access the element from this object you need to retrieve the element from the object using the bracket notation.
If you were to provide some HTML I would be able to test this and make sure it works, but here is some code that could get you pointed in the right direction to use mostly JQuery instead of relying on for loops with JavaScript.
JQuery Rewrite
$("dl dt").each(function(index, element){
if($.inArray(blockedWords,$(element).text()) > -1) {
$(this).parent().css("display", "block");
$(element).parent().css("display", "block");
}
})
The Answer To Your Specific Question
Change this:
$(this).parent().style.display= "none"; // 1st Method or
$("dl dt")[i].parent().style.display= "none"; // 2nd Method
to this:
$(this).parent()[0].style.display= "none"; // 1st Method or
$($("dl dt")[i]).parent()[0].style.display= "none"; // 2nd Method
optionally, you can instead use JQuery's css function like this:
$(this).parent().css("display", "none"); // 1st Method or
$($("dl dt")[i]).parent().css("display","none"); // 2nd Method

What is use of $.map function in javascript?

Note: I just want to to understand what is $.map doing in following code..
I am working on openstack horizon,In one of the javascript file they are using $.map function Please seehorizon.d3linechar.js
My question is how $.map is works, what is $ before map. $.map is associated with javascript or jquery..
$.map(self.series, function(serie) {
serie.color = last_point_color = self.color(serie.name);
$.map(serie.data, function(statistic) {
// need to parse each date
statistic.x = d3.time.format.utc('%Y-%m-%dT%H:%M:%S').parse(statistic.x);
statistic.x = statistic.x.getTime() / 1000;
last_point = statistic;
last_point.color = serie.color;
});
});
Please read the jQuery documentation. Their are many many examples. Our folk is realy trying to help you. But what is the lack of your understanding in the $.map() function?
$ is only the namespace and makes at least the map function work. So forget about it.
map( input, outputFunction ) is iterating through the input which has to be an real array. The outputFunction, usually a self executing function, is able to manipulate the content of each element of the inputed array.
In your example:
$.map(self.series, function(serie) {
self.series is the input and each element of that array will be called as serie in the anonymous or rather self executed function.
serie.color = last_point_color = self.color(serie.name);
Change some color stuff...
$.map(serie.data, function(statistic) {
Next call of the mapping function.
// need to parse each date
statistic.x = d3.time.format.utc('%Y-%m-%dT%H:%M:%S').parse(statistic.x);
Parsing the date to a specific format like discribed in the comment.
statistic.x = statistic.x.getTime() / 1000;
Take the x value parse to a time or maybe to seconds and divide through 1000.
last_point = statistic;
Save element of serie.data to a temporar variable.
last_point.color = serie.color;
Save the color of the of the serie to the element of that serie.
});
});
All in all...
... $.map() iterates through self.series, then iterates through its children and then it looks like it changes the color of every single element to the color of that series.
$ is an alias for the jQuery object.
As for map(), it is an api function in jQuery used to convert the items in an array.
If you look at the source for map, the basic algorithm is to iterate over the passed in elems and obtain a converted value using the callback
function (elems, callback) {
var value, i = 0,
length = elems.length,
ret = [];
for (; i < length; i++) {
//alternatively, for objects, for (i in elems) {
value = callback(elems[i], i);
if (value != null) {
ret.push(value);
}
}
// Flatten any nested arrays
return concat.apply([], ret);
}

change array passed to function

I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help
As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)
Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}

knockout.js computed function to create two arrays?

I have an array of objects that a user can perform search on. I'm using a ko.computed function based on the search to create another array of matched items for display.
self.matchedRecords = ko.computed(function() {
return ko.utils.arrayFilter(self.transponders(), function(r) {
return r.title.toLowerCase().indexOf($('.search-input').val().toLowerCase()) > -1
}
};
This works great and I'm really impressed with the performance so far.
This issue is I also need the "unmatched" records because I have to perform an addition operation on them in some cases (60% of the time). I don't really want to create a second ko.computed function because then I have to run through this array a second time every time a search is performed.
So, my question: Is there a way that I can use the same ko.computed to create a second array of unmatched items? Basically run through the array and put each item in the matched or unmatched array...
If not, is it faster to:
1) create a second ko.computed to get the unmatched items from my array as the user searches; or
2) write an arrayDiff function and determine the unmatched items on demand if I need them.
Cheers!
If you are worried about performance, you could have a non-observable array that you populate while you iterate the search results in your computed. Also note that you are repeatedly selecting using jQuery inside your loop, which I think negates any KO-caused slowdowns.
self.missedRecords = [];
self.matchedRecords = ko.computed(function() {
var searchQuery = $('.search-input').val().toLowerCase(),
transponders = self.transponders(),
matched = [];
// Clear out missed records
self.missedRecords.length = 0;
_.each(transponders, function(transponder) {
if (transponder.title.toLowerCase().indexOf(searchQuery) >= 0) {
matched.push(transponder);
} else {
self.missedRecords.push(transponder);
}
});
return matched;
});
I used _.each from Underscore to keep the code shorter. The drawback of this approach is that changes to missedRecords can't (reliably) be bound to the UI (e.g. in case you have a foreach binding).
If you do need the missedRecords array to be observable, and still want to keep things fast(er), you could do something like this:
self.missedRecords = ko.observableArray([]);
self.matchedRecords = ko.computed(function() {
var searchQuery = $('.search-input').val().toLowerCase(),
transponders = self.transponders(),
matched = [],
missed = [];
_.each(transponders, function(transponder) {
if (transponder.title.toLowerCase().indexOf(searchQuery) >= 0) {
matched.push(transponder);
} else {
missed.push(transponder);
}
});
// Clear out missed records, without triggering subscriptions
self.missedRecords().length = 0;
// Copy the local missed array to the KO observable array
// This will NOT trigger notifications
ko.utils.arrayPushAll(self.missedRecords(), missed);
// Tell KO that the observable array has mutated - this will trigger changes
// to anything observing the missedRecords array
self.missedRecords.valueHasMutated();
return matched;
});
You could also skip computed altogether and just subscribe to changes to change the state of your arrays. For example:
self.missedRecords = ko.observableArray([]);
self.matchedRecords = ko.observableArray([]);
self.transponders.subscribe(function(newTransponders) {
var matched = [],
missed = [];
_.each(newTransponders, function(transponder) {
// Populate matched/missed local arrays
});
// Copy the arrays to the observableArray instances using the technique above
});

Can I loop through 2 objects at the same time in JavaScript?

related (sort of) to this question. I have written a script that will loop through an object to search for a certain string in the referring URL. The object is as follows:
var searchProviders = {
"google": "google.com",
"bing": "bing.com",
"msn": "search.msn",
"yahoo": "yahoo.co",
"mywebsearch": "mywebsearch.com",
"aol": "search.aol.co",
"baidu": "baidu.co",
"yandex": "yandex.com"
};
The for..in loop I have used to loop through this is:
for (var mc_u20 in mc_searchProviders && mc_socialNetworks) {
if(!mc_searchProviders.hasOwnProperty(mc_u20)) {continue;}
var mc_URL = mc_searchProviders[mc_u20];
if (mc_refURL.search(mc_URL) != -1) {
mc_trackerReport(mc_u20);
return false;
}
Now I have another object let's call it socialNetworks which has the following construct:
var socialNetworks = {"facebook" : "facebook.co" }
My question is, can I loop through both of these objects using just one function? the reason I ask is the variable mc_u20 you can see is passed back to the mc_trackerReport function and what I need is for the mc_u20 to either pass back a value from the searchProviders object or from the socialNetworks object. Is there a way that I can do this?
EDIT: Apologies as this wasn't explained properly. What I am trying to do is, search the referring URL for a string contained within either of the 2 objects. So for example I'm doing something like:
var mc_refURL = document.referrer +'';
And then searching mc_refURL for one of the keys in the object, e.g. "google.com", "bing.com" etc. 9this currently works (for just one object). The resulting key is then passed to another function. What I need to do is search through the second object too and return that value. Am I just overcomplicating things?
If I understand your question correctly, you have a variable mc_refURL which contains some URL. You want to search through both searchProviders and socialNetworks to see if that URL exists as a value in either object, and if it does you want to call the mc_trackerReport() function with the property name that goes with that URL.
E.g., for mc_refURL === "yahoo.co" you want to call mc_trackerReport("yahoo"), and for mc_ref_URL === "facebook.co" you want to call mc_trackerReport("facebook").
You don't say what to do if the same URL appears in both objects, so I'll assume you want to use whichever is found first.
I wouldn't create a single merged object with all the properties, because that would lose information if the same property name appeared in both original objects with a different URL in each object such as in an example like a searchProvider item "google" : "google.co" and a socialNetworks item "google" : "plus.google.com".
Instead I'd suggest making an array that contains both objects. Loop through that array and at each iteration run your original loop. Something like this:
var urlLists = [
mc_searchProviders,
mc_socialNetworks
],
i,
mc_u20;
for (i = 0; i < urlLists.length; i++) {
for (mc_u20 in urlLists[i]) {
if(!urlLists[i].hasOwnProperty(mc_u20))
continue;
if (mc_refURL.search(urlLists[i][mc_u20]) != -1) {
mc_trackerReport(mc_u20);
return false;
}
}
}
The array of objects approach is efficient, with no copying properties around or anything, and also if you later add another list of URLs, say programmingForums or something you simply add that to the end of the array.
You could combine the two objects into one before your loop. There's several approaches here:
How can I merge properties of two JavaScript objects dynamically?
var everything = searchProviders;
for (var attrname in socialNetworks) { everything[attrname] = socialNetworks[attrname]; }
for(var mc_u20 in everything) {
// ...
}
for (var i = 0; i < mc_searchProviders.length; i++) {
var searchProvider = mc_searchProviders[i];
var socialNetwork = mc_socialNetworks[i];
if (socialNetwork != undefined) {
// Code.
}
}
Or am i horribly misunderstanding something?

Categories