The following code works fine but I want to make it better:
function prodNameTrim(selector){
var el = document.getElementsByClassName(selector);
var len = el.length;
for(i = 0; i<len; i++){
aObj = el[i].getElementsByTagName('a');
txtNode = aObj[0].childNodes[0].nodeValue;
if(txtNode.length > 26){
txtNode = txtNode.substring(0, 27) + ' ...';
}
aObj[0].childNodes[0].nodeValue = txtNode;
}
}
What I don't like about it is that I first establish txtNode before the condition like so:
txtNode = aObj[0].childNodes[0].nodeValue;
After I process the variable through my conditional to truncate the string with an ellipses, I do the following to replace the text in the DOM:
aObj[0].childNodes[0].nodeValue = txtNode;
I have to believe there is a better way to do this but I'm not sure what that is, I feel as if I'm breaking the DRY rule.
I would suggest this:
function prodNameTrim(selector){
var el = document.getElementsByClassName(selector),
len = el.length, node;
for(var i = 0; i < len; i++) {
node = el[i].getElementsByTagName('a')[0].firstChild;
if(node.nodeValue.length > 26){
node.nodeValue = node.nodeValue.substring(0, 27) + ' ...';
}
}
}
Changes:
Declare all local variables (so no implicit globals - you weren't declaring i, aObj or txtNode)
Skip the aObj intermediate as it isn't needed
Avoid the textNode intermediate as it isn't need
Assign directly to nodeValue
Make the assignment to nodeValue only inside the if statement since that's the only place it changes
Use .firstChild instead of .childNodes[0]
I wonder if this would work (probably requires IE9 or higher and would like to see the HTML to know for sure and run some browser tests), but the general idea is to use querySelectorAll() and combine the two searches into one more involved CSS selector:
function prodNameTrim(rootClass) {
var items = document.querySelectorAll("." + rootClass + " a:first-of-type"), node;
for (var i = 0; i < items.length; i++) {
node = items[i].firstChild;
if (node.nodeValue.length > 26) {
node.nodeValue = node.nodeValue.substring(0, 27) + ' ...';
}
}
}
Note, this second implementation assumes that the argument to prodNameTrim() is a class name (as it was in the OP's version).
Or, if there's only one link tag in each selector parent, then you could simply use this which should work in all modern browsers:
function prodNameTrim(rootClass) {
var items = document.querySelectorAll("." + rootClass + " a"), node;
for (var i = 0; i < items.length; i++) {
node = items[i].firstChild;
if (node.nodeValue.length > 26) {
node.nodeValue = node.nodeValue.substring(0, 27) + ' ...';
}
}
}
If you just want to avoid repeating the aObj[0].childNodes[0] part everywhere you can use your txtNode variable to refer to the node itself, rather than its value:
function prodNameTrim(selector){
var el = document.getElementsByClassName(selector);
var len = el.length;
var txtNode; // declare txtNode with var
for(var i = 0; i<len; i++){
txtNode = el[i].getElementsByTagName('a')[0].childNodes[0];
if(txtNode.nodeValue.length > 26){
txtNode.nodeValue = txtNode.nodeValue.substring(0, 27) + ' ...';
}
}
}
You'll notice that you still end up repeating txtNode.nodeValue everywhere, but it's better than having to include aObj[0].childNodes[0] every time. And the line that you had after the if statement to write the substring version back to the node is not needed, since that update now takes place directly inside the if.
Note also that you should declare all of your variables with var or they'll become globals. (And the way I've shown above doesn't actually need the aObj variable at all.)
If you'd like to do the conditional truncation in one line, you could do something like this:
function prodNameTrim(selector){
var el = document.getElementsByClassName(selector),
len = el.length, node;
for(var i = 0; i < len; i++) {
node = el[i].getElementsByTagName('a')[0].firstChild;
node.nodeValue.substring(0, 27) + (node.nodeValue.length > 26 ? ' ...', '')
}
}
You are doing it the right way – except for the fact that you're introducing an unnecessary global variable. The first assignment should be:
var txtNode = aObj[0].childNodes[0].nodeValue;
Use the var statement! You can also declare the variable before the for loop, at the top of the function. As noted by nnnnnn, you're also creating unintended globals for i and aObj. So you could fix all three problems by inserting this right before the for loop:
var i, aObj, txtNode;
Related
I have tried Googling this question but no luck. Probably because I'm asking the wrong way. Any help is much appreciated.
I have variables copy1, copy2, etc. I want to iterate through them and select each one to check if it's contents has a certain number of characters. When I use any variation of the below, it will either console an error or output a string in the console.
var copy1 = document.getElementById('copy1');
var copy2 = document.getElementById('copy2');
var copy3 = document.getElementById('copy3');
for(var i=0;i<4;i++){
console.log(copy+i);
console.log("copy"+i);
};
Ideally I would be able to select an element and style that via javascript.
Much appreciated
Thanks All.
Moe
Agree with #jaromanda-x:
var copy1 = document.getElementById('copy1');
var copy2 = document.getElementById('copy2');
var copy3 = document.getElementById('copy3');
for (var i=1; i<4; i++) {
console.log(window['copy'+i]);
};
Or you can use more simple example, like:
for (var i=1; i<4; i++) {
var name = 'copy' + i;
console.log(document.getElementById(name));
};
Or even:
for (var i=1; i<4; i++) {
console.log(document.getElementById('copy' + i));
};
You can store the properties in an object where values are set to the DOM element
let copies = {
1 : document.getElementById('copy1'),
2 : document.getElementById('copy2'),
3 : document.getElementById('copy3')
}
for (let [key, prop] of Object.entries(copies)) {
console.log(key, prop)
}
console.log(copies[1], copies[2], copies[3]);
Or use attribute begins with and attribute ends with selectors with .querySelector()
let n = 1;
let copy1 = document.querySelector(`[id^=copy][id$='${n}']`); // `#copy1`
let copy2 = document.querySelector(`[id^=copy][id$='${++n}']`); // `#copy2`
for (let i = 1; i < 4; i++) {
console.log(document.querySelector("[id^=copy][id$=" + i + "]"));
}
Since nobody has addressed your "certain number of characters" requirement yet, I thought I would.
You could always use jQuery or write your own $ method, which works as a document.getElementById() wrapper function.
Here is a jsfiddle to see it in action.
HTML
<div id="copy1">01234</div>
<div id="copy2">012345678</div>
<div id="copy3">0123456789 0123456789</div>
JavaScript
// Isn't this just a brilliant short-cut?
function $(id) {
return document.getElementById(id);
}
for (let i=1; i<4; i++){
let obj = $('copy' + i);
let value = (obj) ? obj.innerText : '';
console.log('value.length:', value.length);
};
I am practicing my javascript. I have created a link to show hide paragraphs. The code currently uses 2 'for' loops. Should I somehow be creating a function for the 'for' loop and then re-use the function?
var paragraphs = document.getElementsByTagName('p'),
firstParagraph = paragraphs[0],
link = document.createElement('a');
link.innerHTML = 'Show more';
link.setAttribute('class', 'link');
link.setAttribute('href', '#');
firstParagraph.appendChild(link);
for (var i = 1; i <= paragraphs.length - 1; i++) {
paragraphs[i].classList.add('hide')
}
function toggleHide(e) {
e.preventDefault;
var paragraphs = document.getElementsByTagName('p');
for (i = 1; i <= paragraphs.length - 1; i++) {
paragraphs[i].classList.toggle('hide');
}
}
link.addEventListener('click', toggleHide)
Since toggle('hide') will also do the same thing of add('hide') when initializing the paragraph list, it is good to pull up the duplicate code to a single function.
For example:
var paragraphs = document.getElementsByTagName('p'),
firstParagraph = paragraphs[0],
link = document.createElement('a');
link.innerHTML = 'Show more';
link.setAttribute('class' , 'link');
link.setAttribute('href' , '#');
firstParagraph.appendChild(link);
toggleHideAll();
function toggleHide( e ){
e.preventDefault;
var paragraphs = document.getElementsByTagName('p');
toggleHideAll();
}
function toggleHideAll(){
for( i = 1 ; i <= paragraphs.length-1 ; i++){
paragraphs[i].classList.toggle('hide');
}
}
link.addEventListener( 'click' , toggleHide)
Yes, a single loop to achieve both ends would be good, as #Solmon says:
function toggleHideAll(){
for (var i = 1; i <= paragraphs.length-1; i++) {
paragraphs[i].classList.toggle('hide');
}
}
There is a more idiomatic way to express this loop, however, and I would advise you to use it, because the original form is confusing to developers who are accustomed to the standard form:
function toggleHideAll() {
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].classList.toggle('hide');
}
}
That is, loop starting at zero, while the loop variable is less than length (not less than or equal to length minus one. And in this case, the loop does not do exactly the same as your original, because the original actually skips your first paragraph. If that's intentional, rather than tweaking the loop parameters, I would recommend toggling all of the paragraphs and then handling the special case with a line of code outside the loop:
function toggleHideAll() {
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].classList.toggle('hide');
}
paragraphs[0].classList.remove('hide');
}
Also, it's really nice when you can avoid explicit loops in your code altogether:
function toggleHideAll() {
paragraphs.forEach(p => p.classList.toggle('hide'));
paragraphs[0].classList.remove('hide');
}
I'm attempting to teach myself javascript. I chose something I assumed was simple, but ran into problems relatively quickly.
I'm attempting to search a string for another string given by the user.
My code so far is:
var source = "XREs2qqAQfjr6NZs6H5wkZdOES5mikexRkOPsj6grQiYNZfFoqXI4Nnc1iONKVrA";
var searchString = []; //the users input
searchString = prompt("Enter search string");
var hits = [];
var one = 0;
var two = 0;
var k = 0;
var sourceSearch = function(text) {
for(i = 0; i < source.length; i++) { //for each character in the source
if(source[i] === searchString[0]) { //if a character in source matches the first element in the users input
one = source.indexOf(i); //confused from here on
for(p = searchString.length; p > 0; p--) {
}
}
}
};
sourceSearch(searchString);
My idea was:
check to see if the first loop finds a character that matches the first character in the user input
if it matches, check to see if the next X characters after the first match the next X characters in the source string
if they all match, push them to the hits array
My problem: I have no idea how to iterate along the arrays without nesting quite a few if statements, and even then, that wouldn't be sufficient, considering I want the program to work with any input.
Any ideas would be helpful. Thanks very much in advance.
Note: There are a few un-used variables from ideas I was testing, but I couldn't make them work.
You can try:
if (source.indexOf(searchString) !== -1) {
// Match!
}
else
{
//No Match!
}
As the other answers so far point out, JavaScript strings have an indexOf function that does what you want. If you want to see how it's done "by hand", you can modify your function like this:
var sourceSearch = function(text) {
var i, j, ok; // always declare your local variables. globals are evil!
// for each start position
for(i = 0; i < source.length; i++) {
ok = true;
// check for a match
for (j = searchString.length - 1; ok && j >= 0; --j) {
ok = source[i + j] === searchString[j];
}
if (ok) {
// searchString found starting at index i in source
}
}
};
This function will find all positions in source at which searchString was found. (Of course, you could break out of the loop on the first success.) The logic is to use the outer loop to advance to each candidate start position in source and use the inner loop to test whether that position actually is the position of a match to searchString.
This is not the best algorithm for searching strings. The built-in algorithm is much faster (both because it is a better algorithm and because it is native code).
to follow your approach, you can just play with 2 indexes:
var sourceSearch = function(text) {
j = 0;
for(i = 0; i < source.length; i++) {
if(source[i] === text[j]) {
j++;
} else {
j = 0;
}
if (j == text.length) {
console.log(i - j); //this prints the starting index of the matching substring
}
}
};
These answers are all pretty good, but I'd probably opt for something like this:
var source = "XREs2qqAQfjr6NZs6H5wkZdOES5mikexRkOPsj6grQiYNZfFoqXI4Nnc1iONKVrA";
var searchString = []; //the users input
searchString = prompt("Enter search string");
var hits = source.split(searchString);
var hitsCount = hits.length - 1;
This way you have all of the data you need to figure out where each hit occurred in he source, if that's important to you.
I need to create javascript objects that base on user defined number. So if user defines 20, then I need to create 20 variables.
var interval_1=0, interval_2=0, interval_3=0, interval_4=0, interval_5=0... interval_20=0;
how do I do it so the name of the object can be dynamically created?
for (i=0; i<=interval; i++){
var interval_ + i.toString() = i;
}
Erm, use an array?
for( i=0; i<=count; i++) array[i] = i;
Use an array:
var i, count, interval = [];
// user defines count, 20 for example
count = 20;
for (i = 0; i < count; i++) {
interval.push(i);
}
// interval[0] === 0
// interval[19] === 19
// interval.length === 20
Note, this starts the index at 0 and goes up to count - 1. Do not use i <= count unless you start i at 1.
Here is a jsFiddle to illustrate. Hit F12 to open dev tools in most browsers and look at console, or change console.log() to alert().
Link: http://jsfiddle.net/willslab/CapBN/1/
Alternatively, you could setup a single object with properties for each value:
var i, count, intervals = {};
count = 20;
for (i = 0; i < count; i++) {
intervals["interval_" + i] = i;
}
//intervals.interval_0 === 0
//intervals.interval_19 === 19)
Link: http://jsfiddle.net/willslab/EBjx7/2/
for (i=0; i<=20; i++){
window["interval_" + i.toString()] = i;
}
Javascript variables can be created by:
a variable declaration, e.g. var x;
assigning a value to an undeclared variable, e.g. y = 'foo';
an identifier in a formal parameter list, e.g. function bar(x, y, z);
using eval, e.g. eval( 'var x = 4');
If all else fails and you want say 5 variables, you can do:
var s = [];
var i = 5;
while (i--) {
s[i] = 'a' + i;
}
eval('var ' + s.join(',') + ';');
alert(a0); // shows undefined
If a0 wasn't defined, the last step would throw a reference error.
Of course the issue you now have is how to access them. If they are created as global variables, you can use:
globalObj['a' + i];
where globalObj is usually window, however there is no equivalent for accessing function variables since you can't access their variable object.
So the usual solution is to put things into arrays or objects where you can iterate over the properties to find things you don't know the name of.
Is there a way to make the following code faster? It's becoming too slow, when length of array is more than 1000 records, especially in IE6.
dbusers = data.split(";");
$("#users").html("");
for (i = 0; i < dbusers.length; i++) {
if ($("#username").val() != "") {
if (dbusers[i].indexOf($("#username").val()) != -1) {
$("#users").append(dbusers[i] + "<br>");
}
} else {
$("#users").append(dbusers[i] + "<br>");
}
}
Minimize the amount of work you do in the loop. Don't add stuff to the DOM in the loop, create a string.
var dbusers = data.split(";");
var username = $("#username").val();
var userlist = "";
if (username == "") {
for (i = 0; i < dbusers.length; i++) {
userlist += dbusers[i] + "<br>";
}
} else {
for (i = 0; i < dbusers.length; i++) {
if (dbusers[i].indexOf(username) != -1) {
userlist += dbusers[i] + "<br>";
}
}
}
$("#users").html(userlist);
Faster than those by far (especially in IE!) is to build your string as an array (yes, really) and then concatenate it at the end:
var dbusers = data.split(";"), username = $('#username').val();
$("#users").html($.map(dbusers, function(_, dbuser) {
if (username == '' || dbuser.indexOf(username) > 0)
return dbuser + '<br>';
return '';
}).get().join(''));
The $.map() routine will build an array from the return values of the function you pass. Here, my function is returning the user string followed by the <br>. The resulting array is then turned into a string by calling the native join() routine. Especially when you've got like 1000 things to work with, this will be much faster than building a string with repeated calls to +=! Try the two versions and compare!
Use a document fragment.
You can perform more optimizations, too, like removing that nasty if and creating the nodes yourself.
var frag = document.createDocumentFragment(),
dbUsers = data.split(';'),
dbUsersLength = dbUsers.length,
curDbUser,
usernameVal = $('#username').val();
for(i = 0; i < dbUsersLength; ++i) {
curDbUser = dbUsers[i];
if(curDbUser.indexOf(usernameVal) !== -1) {
frag.appendChild(document.createTextNode(curDbUser));
frag.appendChild(document.createElement('br'));
}
}
$('#users').empty().append(frag);
I made a tool to benchmark all the current answers: http://dev.liranuna.com/strager/stee1rat.html
ghoppe's and mine seem to be the fastest.
IE6 doesn't support querySelector, so lookups can be particularly slow. Keep HTML manipulation within loops to a minimum by reducing the number of appends you do, each one has a regular expression run on it to extract the HTML and convert it to a DOM object. Also work in some micro optimisations where you can, might improve performance a little especially over thousands of iterations.
var usersEl = $("#users"); // reduce lookups to the #users element
var result = ""; // create a variable for the HTML string
var unameVal = $("#username").val(); // do the username value lookup only once
dbusers = data.split(";");
usersEl.html("");
// Store the length of the array in a var in your loop to prevent multiple lookups
for (var i = 0, max = dbusers.length; i < max; i++) {
if (unameVal !== "") {
if (dbusers[i].indexOf(unameVal) != -1) {
result += dbusers[i] + "<br>";
}
} else {
result += dbusers[i] + "<br>";
}
}
usersEl.html(result); // Set the HTML only once, saves multiple regexes